Using service and callback interfaces

For instance, you may define the following generic service and callback to retrieve a picture from a remote camera, using mORMot's interface-based approach:

type
  // define some custom types to make the implicit explicit
  TCameraID = RawUTF8;
  TPictureID = RawUTF8;
  // mORMot notifications using a callback interface definition
  IMyCameraCallback = interface(IInvokable)
    ['{445F967F-79C0-4735-A972-0BED6CC63D1D}']
    procedure Started(const Camera: TCameraID; const Picture: TPictureID);
    procedure Progressed(const Camera: TCameraID; const Picture: TPictureID;
      CurrentSize,TotalSize: cardinal);
    procedure Finished(const Camera: TCameraID; const Picture: TPictureID;
      const PublicURI: RawUTF8; TotalSize: cardinal);
    procedure ErrorOccured(const Camera: TCameraID; const Picture: TPictureID;
      const MessageText: RawUTF8);
  end;
  // mORMot main service, also defined as an interface
  IMyCameraService = interface(IInvokable)
    ['{3CE61E74-A01D-41F5-A414-94F204F140E1}']
    function TakePicture(const Camera: TCameraID; const Callback: IMyCameraCallback): TPictureID;
  end;

In a single look, I guess you did get the expectation of the "Camera Service".
Take a deep breath, and keep in mind those two type definitions as reference.
We will now compare with a classical message-based pattern.

Classical message(s) event

With a class-based message kind of implementation, you may either have to define a single class, containing all potential information:

type
  // a single class message would need a status
  TMyCameraCallbackState = (
    ccsStarted, ccsProgressed, ccsFinished, ccsErrorOccured);
  // the single class message
  TMyCameraCallbackMessage = class
  private
    fCamera: TCameraID;
    fPicture: TPictureID;
    fTotalSize: cardinal;
    fMessageText: RawUTF8;
    fState: TMyCameraCallbackState;
  published
    property State: TMyCameraCallbackState read fState write fState;
    property Camera: TCameraID read fCamera write fCamera;
    property Picture: TPictureID read fPicture write fPicture;
    property TotalSize: cardinal read fTotalSize write fTotalSize;
    property MessageText: RawUTF8 read fMessageText write fMessageText;
  end;

This single class is easy to write, but makes it a bit confusing to consume the notification. Which field comes with which state? The client-side code would eventually consist of a huge case aMessage.State of ... block, with potential issues. The business logic does not appear in this type definition. Easy to write, difficult to read - and maintain...

In order to have an implementation closer to SOLID design principles, you may define a set of classes, as such:

type
  // all classes would inherit from this one, to have common properties
  TMyCameraCallbackAbstract = class
  private
    fCamera: TCameraID;
    fPicture: TPictureID;
  published
    property Camera: TCameraID read fCamera write fCamera;
    property Picture: TPictureID read fPicture write fPicture;
  end;
  // message class when the picture acquisition starts
  TMyCameraCallbackStarted = class(TMyCameraCallbackAbstract);
  // message class when the picture is acquired
  TMyCameraCallbackFinished = class(TMyCameraCallbackAbstract)
  private
    fPublicURI: RawUTF8;
    fTotalSize: cardinal;
  published
    property TotalSize: cardinal read fTotalSize write fTotalSize;
    property PublicURI: RawUTF8 read fPublicURI write fPublicURI;
  end;
  // message during picture download
  TMyCameraCallbackProgressed = class(TMyCameraCallbackFinished)
  private
    fCurrentSize: cardinal;
  published
    property CurrentSize: cardinal read fCurrentSize write fCurrentSize;
  end;
  // error message
  TMyCameraCallbackErrorOccured = class(TMyCameraCallbackAbstract)
  private
    fMessageText: RawUTF8;
  published
    property MessageText: RawUTF8 read fMessageText write fMessageText;
  end;

Inheritance makes this class hierarchy not as verbose as it may have been with plain "flat" classes, but it is still much less readable than the IMyCameraCallback type definition.

In both cases, such class definitions make it difficult to guess which message does match with a given service. You must be very careful and consistent about your naming conventions, and uncouple your service definitions in clear name spaces.

When implementing SOA services, DDD's Ubiquitous Language tends to be polluted by the class definition (getters and setters), and implementation details of the messages-based notification: your Domain code would be tied to the message oriented nature of the Infrastructure layer. interface callbacks would therefore help implementing DDD's Event-Driven pattern, in a cleaner way.

Workflow adaptation

Sometimes, it may be necessary to react to some unexpected event. The consumer may be able to change the workflow of the producer, depending on some business rules, or user expectations. By definition, all message-based implementation are asynchronous: as a result, implementing "reverse" messaging tends to be difficult to write and debug.

A common implementation is to have a dedicated set of "answer" messages, to notify the service providers of a state change - it comes with potential race conditions, or unexpected rebound phenomenons, for instance when you add a node to an existing event-driven system.

Another solution may be to define explicit rules for service providers, e.g. when the service is called. You may define a set of workflows, injected to the provider service at runtime. It will definitively tend to break the Single Responsibility Principle.

On the other hand, since mORMot's callbacks are true interface methods, they may return some values (as a function result or a var/out parameter). On the server side, such callbacks would block and wait for the client end to respond.

So by writing an additional method like:

  IMyCameraCallback = interface(IInvokable)
  ...
    function ShouldRetryIfBusy(const Camera: TCameraID; const Picture: TPictureID): boolean;
  ...

... you would be able to implement any needed complex workflow adaptation, in real time.
The server side code would still be very readable and efficient, with no complex plumbing, wait queue or state machine to set up.

From interfaces come abstraction and ease

As an additional benefit, integration with the Delphi language is clearly implementation agnostic: you are not even tied to use the framework, when working with such interface type definitions. In fact, this is a good way of implementing callbacks conforming to SOLID design principles on the server side, and let the mORMot framework publish this mechanism in a client/server way, by using WebSockets, only if necessary.

The very same code could be used on the server side, with no transmission nor marshalling overhead (via direct interface instance calls), and over a network, with optimized use of resource and bandwidth (via "fake" interface calls, and binary/JSON marshalling over TCP/IP).

On the server side, your code - especially your Domain code - may interact directly with the lower level services, defined in the Domain as interface types, and implemented in the infrastructure layer. You may host both Domain and Infrastructure code in a single server executable, with direct assignment of local class instance as callbacks. This will minimize the program resources, in both CPU and memory terms - which is always a very valuable goal, for any business system.

Last but not least, using an interface would help implementing the whole callback mechanism using Stubs and mocks, e.g. for easy unit testing via Calls tracing.
You may also write your unit tests with real local callback class instances, which would be much easier to debug than over the whole client/server stack. Once you identified a scenario which fails the system, you could reproduce it with a dedicated test, even in an aggressive multi-threaded way, then use the debugger to trace the execution and identify the root cause of the issue.