The most powerful way of writing REST services is to define them via interfaces, then let the SOA/REST framework do all the routing, data marshalling and communication behind the scenes. One distinctive feature of mORMot is to define a method parameter as a notification interface, and let the server call back the client when needed, as with regular Delphi code. This session will present the benefit of defining REST services using interfaces, and how WebSockets can offer real-time notifications into your rich Delphi client applications.
6. interfaces
• In modern object pascal
interface defines a type
with abstract virtual methods
Declaration of functionality
without any implementation
"what" not "how"
7. interfaces
• Definition
type
ICalculator = interface(IInvokable)
['{9A60C8ED-CEB2-4E09-87D4-4A16F496E5FE}']
/// add two signed 32 bit integers
function Add(n1, n2: integer): integer;
end;
8. interfaces
• Implementation
type
TServiceCalculator = class(TInterfacedObject, ICalculator)
…
public
function Add(n1, n2: integer): integer;
end;
function TServiceCalculator.Add(n1, n2: integer): integer;
begin
result := n1 + n2;
end;
9. interfaces
• Implementations
type
TServiceKalculator = class(TInterfacedObject, ICalculator)
…
public
function Add(n1, n2: integer): integer;
end;
function TServiceKalculator.Add(n1, n2: integer): integer;
begin
result := n2 + n1;
end;
10. interfaces
• Usage
function MyAdd(a, b: integer): integer;
var
calc: ICalculator;
begin
calc := TServiceCalculator.Create;
result := calc.Add(a, b);
end;
11. interfaces
• Compiler Magic
function MyAdd(a, b: integer): integer;
var
calc: ICalculator;
begin
calc := nil;
try
calc := TServiceCalculator.Create;
result := calc.Add(a, b);
finally
calc := nil;
end;
end;
12. interfaces
• Reference Counting
function MyAdd(a, b: integer): integer;
var
calc: ICalculator;
begin
calc := nil; refcnt=none
try
calc := TServiceCalculator.Create; refcnt=1
result := calc.Add(a, b);
finally
calc := nil; refcnt=0 -> Free
end;
end;
13. interfaces
• More Magic
function MyAdd(a, b: integer): integer;
var
instance: TServiceCalculator; TInterfacedObject
calc: ICalculator;
begin
calc := nil; refcnt=none
try
instance := TServiceCalculator.Create; refcnt=0
calc := instance; instance._AddRef -> refcnt=1
result := calc.Add(a, b);
finally
calc := nil; instance._Release -> refcnt=0 -> Free
14. interfaces
• Direct Injection
function MyAdd(const calc: ICalculator;
a, b: integer): integer;
begin
result := calc.Add(a, b);
end;
...
Label1.Caption := IntToStr(MyAdd(
TServiceCalculator.Create,
StrToInt(Edit1.Text), StrToInt(Edit2.Text)));
15. interfaces
• Singleton
var
GlobalCalc: ICalculator;
function MyAdd(a, b: integer): integer;
begin
result := GlobalCalc.Add(a, b);
end;
...
GlobalCalc := TServiceCalculator.Create;
...
Label1.Caption := IntToStr(MyAdd(
StrToInt(Edit1.Text), StrToInt(Edit2.Text)));
16. interfaces
• Constructor Injection
type
TMyWork = class
protected
fCalc: ICalculator;
public
constructor Create(const Calculator: ICalculator);
function Add(a, b: integer): integer;
...
constructor TMyWork.Create(const Calculator: ICalculator);
begin
fCalc := Calculator;
...
17. interfaces
• Constructor Injection
type
TMyWork = class
protected
fCalc: ICalculator;
public
constructor Create(const Calculator: ICalculator);
...
function TMyWork.Add(a, b: integer): integer;
begin
result := fCalc.Add(a, b);
end;
19. interfaces
• Constructor Injection
type
TMyWork = class
protected
fCalc: ICalculator;
fConvert: IConvertor;
public
constructor Create(const Calculator: ICalculator;
const Convertor: IConvertor );
...
function TMyWork.AddMul(a, b, c: integer): double;
begin
result := fConvert.AdaptToDouble(
fCalc.Multiply(fCalc.Add(a, b), c));
20. interfaces
• Resolver
function MyAdd(a, b: integer): integer;
var
calc: ICalculator;
begin
// search for ['{9A60C8ED-CEB2-4E09-87D4-4A16F496E5FE}']
if Resolver.TryResolve(ICalculator, calc) then
result := calc.Add(a, b)
else
raise Exception.Create('No ICalculator!');
end;
21. interfaces
• Resolver
function MyAdd(a, b: integer): integer;
var
calc: ICalculator;
begin
// search for ['{9A60C8ED-CEB2-4E09-87D4-4A16F496E5FE}']
Resolver.Resolve(ICalculator, calc);
result := calc.Add(a, b);
end;
22. interfaces
• mORMot TInjectableObject
type
TMyClass = class(TInjectableObject)
...
function TMyClass.MyWork(a, b: integer): integer;
var
calc: ICalculator;
begin
Resolve(ICalculator, calc); // request on need
result := calc.Add(a, b) + 10;
end;
23. interfaces
• mORMot TInjectableObject
type
TMyClass = class(TInjectableObject)
...
published
property Calc: ICalculator // resolved using RTTI
read fCalc;
...
function TMyClass.MyWork(a, b: integer): integer;
begin
result := Calc.Add(a, b) + 10;
end;
24. interfaces
• From Abstraction Comes the Light
Focus on contracts, not completion
Favor logic over implementation
Dependency Injection / Inversion Of Concern
Late binding/resolution
Local/remote execution (SOA)
Stub/mock tooling
28. Service Oriented Architecture
A flexible set of design principles
used during the phases of
systems development and integration
To package functionality
as a suite of inter-operable services
that can be used
within multiple, separate systems
from several business domains
29. Service Oriented Architecture
• Software Service:
A consumer asks a producer
to act in order to produce a result
Invocation is (often) free from previous invocation
(stateless), to minimize resource consumption
Should be uncoupled / unassociated
30. Service Oriented Architecture
• Software Service:
Should be uncoupled / unassociated
Consumers Service Bus Publishers
Client A Publisher 1
Publisher 2
Client B
Publisher 3
Client C
Service 1
Service 2
Service 3
31. Service Oriented Architecture
• Software Service:
Should be uncoupled / unassociated
Consumers Application Service Bus Application Publishers
Business Service Bus Business Publishers
Client A
Composition
Publisher
Composition
Service
Publisher 1
Publisher 2
Publisher 3
Service 1
Service 2
Service 3
33. Micro Service Oriented Architecture
• Single responsibility principle
• Open/closed principle
• Liskov substitution principle (design by contract)
• Interface segregation principle
• Dependency inversion principle
34. Micro Service Oriented Architecture
• Single responsibility principle
Microservices should have a single axis of change
• Open/closed principle
Microservices should be extendable, not modifiable
• Liskov substitution principle (design by contract)
Microservices should be replaceable
• Interface segregation principle
Several smaller dedicated Microservices
• Dependency inversion principle
Microservices focus on abstraction, not concretion
35. Micro Service Oriented Architecture
if
you write SOLID interface-powered code
and
you define your services as interfaces
then
it is likely/possible/natural
that you end up with Micro Services
36. Micro Service Oriented Architecture
if
you write SOLID interface-powered code
and
you define your services as interfaces
then
it is likely/possible/natural (but not sure!)
that you end up with Micro Services
39. REST HTTP WS w/ mORMot
/// Framework Core Low-Level Interface/SOLID Processing
// - this unit is a part of the Open Source Synopse mORMot framework 2,
// licensed under a MPL/GPL/LGPL three license - see LICENSE.md
unit mormot.core.interfaces;
{
*****************************************************************************
Implements SOLID Process via Interface types
- IInvokable Interface Methods and Parameters RTTI Extraction
- TInterfaceFactory Generating Runtime Implementation Class
- TInterfaceResolver TInjectableObject for IoC / Dependency Injection
- TInterfaceStub TInterfaceMock for Dependency Mocking
- TInterfacedObjectFake with JITted Methods Execution
- TInterfaceMethodExecute for Method Execution from JSON
- SetWeak and SetWeakZero Weak Interface Reference
*****************************************************************************
}
40. REST HTTP WS w/ mORMot
/// Framework Core Low-Level Interface/SOLID Processing
// - this unit is a part of the Open Source Synopse mORMot framework 2,
// licensed under a MPL/GPL/LGPL three license - see LICENSE.md
unit mormot.core.interfaces;
{
*****************************************************************************
Implements SOLID Process via Interface types
- IInvokable Interface Methods and Parameters RTTI Extraction
- TInterfaceFactory Generating Runtime Implementation Class
- TInterfaceResolver TInjectableObject for IoC / Dependency Injection
- TInterfaceStub TInterfaceMock for Dependency Mocking
- TInterfacedObjectFake with JITted Methods Execution
- TInterfaceMethodExecute for Method Execution from JSON
- SetWeak and SetWeakZero Weak Interface Reference
*****************************************************************************
}
42. REST HTTP WS w/ mORMot
/// REpresentation State Tranfer (REST) Types and Classes on Server Side
// - this unit is a part of the Open Source Synopse mORMot framework 2,
// licensed under a MPL/GPL/LGPL three license - see LICENSE.md
unit mormot.rest.server;
{
*****************************************************************************
Server-Side REST Process
- TRestServerUriContext Access to the Server-Side Execution
- TRestServerRoutingRest/TRestServerRoutingJsonRpc Requests Parsing Scheme
- TAuthSession for In-Memory User Sessions
- TRestServerAuthentication Implementing Authentication Schemes
- TRestServerMonitor for High-Level Statistics of a REST Server
- TRestServer Abstract REST Server
- TRestHttpServerDefinition Settings for a HTTP Server
*****************************************************************************
}
43. REST HTTP WS w/ mORMot
• Host and publish the service
Server.ServiceRegister(
TServiceCalculator, [TypeInfo(ICalculator)], sicShared);
will register the TServiceCalculator class
to implement the ICalculator service
with a single shared instance life time
to the Server: TRestServer instance
44. REST HTTP WS w/ mORMot
• Host and publish the service
Server.ServiceDefine(
TServiceCalculator, [ICalculator], sicShared);
// register once the TypeInfo() e.g. in the unit defining ICalculator
initialization
TInterfaceFactory.RegisterInterfaces([
TypeInfo(ICalculator),
...]);
45. REST HTTP WS w/ mORMot
• Host and publish the service
46. REST HTTP WS w/ mORMot
• Host and publish the service
in practice, you may mostly use:
sicShared = stateless
= a single instance to rule them all (REST like)
sicClientDriven = stateful
= the client-side interface lifetime
is replicated on the server side (RPC like)
47. REST HTTP WS w/ mORMot
• mormot.rest.server.pas
Server := TRestHttpServer.Create(
RestServerPort, [RestServer]);
= Publish RestServer over
http://localhost:RestServerPort/RestServer.Model.Root
http://localhost:8888/root
51. REST HTTP WS w/ mORMot
• mormot.soa.codegen.pas
server can generate source or documentation:
using customizable Mustache {{templates}}
getting URI and methods/parameters from RTTI
extracting text from source comments
always up-to-date documentation
Swagger to navigate the API or generate code
52. REST HTTP WS w/ mORMot
• mormot.soa.codegen.pas
server can generate source or documentation:
/// supported languages typesets
TWrapperLanguage = (
lngDelphi, lngPascal,
lngCS, lngJava, lngTypeScript,
lngSwagger);
54. REST HTTP WS w/ mORMot
/// REpresentation State Tranfer (REST) Types and Classes on Client side
// - this unit is a part of the Open Source Synopse mORMot framework 2,
// licensed under a MPL/GPL/LGPL three license - see LICENSE.md
unit mormot.rest.client;
{
*****************************************************************************
Client-Side REST Process
- Client Authentication and Authorization Logic
- TRestClientRoutingRest/TRestClientRoutingJsonRpc Routing Schemes
- TRestClientUri Base Class for Actual Clients
- TRestClientLibraryRequest after TRestServer.ExportServerGlobalLibraryRequest
- TInterfacedCallback/TBlockingCallback Classes
*****************************************************************************
}
55. REST HTTP WS w/ mORMot
• Register and run the service on client side
Client.ServiceDefine([ICalculator], sicShared);
• Contract (= type definition) is checked
for consistency between client and server
there are tons of options, e.g. for a JavaScript client
or if you don’t want to verify the contract
56. REST HTTP WS w/ mORMot
• Register and run the service on client side
Client.ServiceDefine([ICalculator], sicShared);
var
calc: ICalculator;
begin
if Client.Services['Calculator'].Get(calc) then
result := calc.Add(10,20);
end;
57. REST HTTP WS w/ mORMot
• Register and run the service on client side
if Client.Services['Calculator'].Get(calc) then
result := calc.Add(10,20);
• Execution will take place on the server side,
using a “fake” class implementing ICalculator
58. REST HTTP WS w/ mORMot
• Register and run the service on client side
if Client.Services['Calculator'].Get(calc) then
result := calc.Add(10,20);
• Execution will take place on the server side,
using a “fake” class implementing ICalculator
JITted on Intel/ARM 32/64-bit directly to/from JSON
59. REST HTTP WS w/ mORMot
• Register and run the service on client side
if Client.Services['Calculator'].Get(calc) then
result := calc.Add(10,20);
• Data is transmitted by representation (REst)
as input / output JSON (array/object) values
POST https://servername/restroot/calculator/add
with {"n1": 10, "n2": 20 } as JSON body
returns HTTP/1.1 200
{ "result": 30 }
60. REST HTTP WS w/ mORMot
• Register and run the service on client side
if Client.Services['Calculator'].Get(calc) then
result := calc.Add(10,20);
• Any server-side exception will be notified
but not transmitted to the client
because exceptions are stateful about server side
because exceptions should be exceptional
rather return enumerate or/and string for errors
61. REST HTTP WS w/ mORMot
• TRestClientUri could be
in-process mormot.rest.client.pas
mormot.rest.sqlite3.pas
over HTTP mormot.rest.http.client.pas
over WebSockets
62. REST HTTP WS w/ mORMot
• TRestClientUri could be
in-process mormot.rest.client.pas
mormot.rest.sqlite3.pas
over HTTP mormot.rest.http.client.pas
over WebSockets
potentially Unix Sockets instead of TCP
e.g. when used behind a reverse proxy
63. REST HTTP WS w/ mORMot
/// REpresentation State Tranfer (REST) HTTP Client
// - this unit is a part of the Open Source Synopse mORMot framework 2,
// licensed under a MPL/GPL/LGPL three license - see LICENSE.md
unit mormot.rest.http.client;
{
*****************************************************************************
Client-Side REST Process over HTTP/WebSockets
- TRestHttpClientGeneric and TRestHttpClientRequest Parent Classes
- TRestHttpClientSocket REST Client Class over Sockets
- TRestHttpClientWebsockets REST Client Class over WebSockets
- TRestHttpClientWinINet TRestHttpClientWinHttp Windows REST Client Classe
- TRestHttpClientCurl REST Client Class over LibCurl
- TRestHttpClient/TRestHttpClients Main Usable Classes
*****************************************************************************
}
65. REST HTTP WS w/ mORMot
• Register and run the service on client side
if Client.Services['Calculator'].Get(calc) then
result := calc.Add(10,20);
• Data is transmitted by representation (REst)
as input / output JSON (array/object) values
emulating HTTP-like requests using WS frames
over a plain WebSockets JSON protocol
or an encrypted binary protocol (public or asymetric key)
66. REST HTTP WS w/ mORMot
• Server Notification via WebSockets
How do server calls
naturally translate
into interface methods?
1€ question
67. REST HTTP WS w/ mORMot
• Server Notification via WebSockets
How do server calls
naturally translate
into interface methods?
Message/event objects?
Notification bus?
WebSockets frames?
70. REST HTTP WS w/ mORMot
• Server Notification via WebSockets
ILongWorkCallback = interface(IInvokable)
['{425BF199-19C7-4B2B-B1A4-A5BE7A9A4748}']
procedure WorkFinished(
const workName: string; timeTaken: integer);
procedure WorkFailed(const workName, error: string);
end;
… just like regular pascal code,
with or without Websockets – could even be mocked!
71. REST HTTP WS w/ mORMot
• Server Notification via WebSockets
ILongWorkCallback = interface(IInvokable)
['{425BF199-19C7-4B2B-B1A4-A5BE7A9A4748}']
procedure WorkFinished(
const workName: string; timeTaken: integer);
procedure WorkFailed(const workName, error: string);
end;
The methods could be non-blocking or blocking,
=procedure = function
72. REST HTTP WS w/ mORMot
• Server Notification via WebSockets
ILongWorkCallback = interface(IInvokable)
['{425BF199-19C7-4B2B-B1A4-A5BE7A9A4748}']
procedure WorkFinished(
const workName: string; timeTaken: integer);
procedure WorkFailed(const workName, error: string);
end;
The procedure calls are not-blocking,
with automatic frames gathering
(better compression, less latency: perfect for Events)
73. REST HTTP WS w/ mORMot
• Server Notification via WebSockets
exrest-websockets
Use the source, Luke!