In fact, Exceptions are not value objects, but true class instances, with some methods, potentially and a specific behavior within the Delphi language. A Delphi exception is something very specific, and would not be easily converted into e.g. a JavaScript, Java or C# exception.
Furthermore, re-creating and raising an instance of the same exception which occurred on the server side would induce a strong dependency of the client code. For instance, if the server side raise a ESQLDBOracle exception, linking your client side with the whole SynDBOracle.pas unit just for exception would be a huge issue. The ESQLDBOracle exception, by itself, contains a link to an Oracle statement instance, which would be lost when transmitted over the wire. Some client platforms (e.g. mobile or AJAX) do not even have any knowledge of what an Oracle database is...
As such, exception are not good candidate on serialization, and transmission per value, from the server side to the client side. We would NOT be in favor of propagating exceptions to the client side.

This is why exceptions should better be intercepted on the server side, with a try .. except block within the service methods, then converted into low level DTO types, specific to the service, then explicitly transmitted as error codes to the client.

For instance, you may use an enumerate, in conjunction with a variant for additional structured information (as a string or a more complex TDocVariant), to transmit an error to the client side.

See for instance how ICQRSQuery, and its associated TCQRSResult enumeration, are defined in mORMotDDD.pas:

type
  TCQRSResult =
    (cqrsSuccess, cqrsSuccessWithMoreData,
     cqrsUnspecifiedError, cqrsBadRequest,
     cqrsNotFound, cqrsNoMoreData, cqrsDataLayerError,
     cqrsInternalError, cqrsDDDValidationFailed,
     cqrsInvalidContent, cqrsAlreadyExists,
     ...

ICQRSQuery = interface(IInvokable) ['{923614C8-A639-45AD-A3A3-4548337923C9}'] function GetLastError: TCQRSResult; function GetLastErrorInfo: variant; end;

The first cqrsSuccess item of the TCQRSResult enumerate will be the default one (mapped and transmitted to a 0 JSON number), so in case of any stub of mock of the interfaces, methods will return as successful, as expected - see Interfaces in practice: dependency injection, stubs and mocks.

When any exception is raised in a service method, a TCQRSResult enumeration value is returned as result, so that error would be transmitted directly:

function TDDDMonitoredDaemon.Stop(out Information: variant): TCQRSResult;
...
begin
  CqrsBeginMethod(qaNone,result);
  try
....
    CqrsSetResult(cqrsSuccess);
  except
    on E: Exception do
      CqrsSetResult(E,cqrsInternalError);
  end;
end;

The mORMotDDD.pas unit defines, in the TCQRSQueryObject abstract class, some protected methods to handle errors and exceptions as expected by ICQRSQuery, e.g. the TCQRSQueryObject.CqrsSetResult() method will set result := cqrsInternalError and serialize the E: Exception within the internal variant used for additional error, ready to be retrieved using ICQRSQuery.GetLastErrorInfo.

Exceptions are very useful to interrupt a process in case of a catastrophic failure, but they are not the best method for transmitting errors over remote services. Some newer languages (e.g. Google's Go), would even not define any exception types, but rely on returned values, to transmit the errors. - see https://golang.org/doc/faq#exceptions - in our client-server error handling design, we followed the same idea.

Feedback is welcome on our forum, as usual!