Software Architecture & Design
Architecture
From n-Tier to SOA
From SOAP to REST
Technical Debt
Design
From SQL to ORM, NoSQL and ODM
From RAD to MVC
SOLID principles
Domain Driven Design (DDD)
Applying patterns on Delphi code using mORMot
Software Architecture & Design
From Interfaces to SOLID
Delphi and interfaces
SOLID design principles
Dependency Injection, stubs and mocks
Using mORMot features
Delphi and Weak pointers
From Interfaces to SOLID
Delphi and interfaces
In Delphi OOP model
An interface defines a type
that comprises abstract virtual methods
It is a declaration of functionality
without an implementation of that functionality
It defines "what" is available,
not "how" it is made available
From Interfaces to SOLID
Delphi and interfaces
Declaring an interface
Naming convention: ICalculator
No visibility attribute: all published
No fields, just methods and properties
Unique identifier by GUID (Ctrl Shift G)
From Interfaces to SOLID
type
ICalculator = interface(IInvokable)
['{9A60C8ED-CEB2-4E09-87D4-4A16F496E5FE}']
/// add two signed 32 bit integers
function Add(n1,n2: integer): integer;
end;
Delphi and interfaces
Implementing an interface
ICalculator added to the “inheritance chain”
TCalculator implements a behavior
From Interfaces to SOLID
type
TServiceCalculator = class(TInterfacedObject, ICalculator)
protected
fBulk: string;
public
function Add(n1,n2: integer): integer;
procedure SetBulk(const aValue: string);
end;
function TServiceCalculator.Add(n1, n2: integer): integer;
begin
result := n1+n2;
end;
procedure TServiceCalculator.SetBulk(const aValue: string);
begin
fBulk := aValue;
end;
Delphi and interfaces
Using an interface
Strong typing
The variable defines a behavior (contract)
Path to abstraction
From Interfaces to SOLID
function MyAdd(a,b: integer): integer;
var Calculator: ICalculator;
begin
Calculator := TServiceCalculator.Create;
result := Calculator.Add(a,b);
end;
Delphi and interfaces
Using an interface
Strong typing
The variable defines a behavior (contract)
Path to abstraction
Automatic try..finally
From Interfaces to SOLID
function MyAdd(a,b: integer): integer;
var Calculator: TServiceCalculator;
begin
Calculator := TServiceCalculator.Create;
try
result := Calculator.Add(a,b);
finally
Calculator.Free;
end;
end;
function MyAdd(a,b: integer): integer;
var Calculator: ICalculator;
begin
Calculator := TServiceCalculator.Create;
result := Calculator.Add(a,b);
end;
Delphi and interfaces
Automatic try..finally
Compiler generates
some hidden code…
Behavior inherited from TInterfacedObject
Similar to COM / ActiveX
From Interfaces to SOLID
function MyAdd(a,b: integer): integer;
var Calculator: ICalculator;
begin
Calculator := TServiceCalculator.Create;
result := Calculator.Add(a,b);
end;
function MyAdd(a,b: integer): integer;
var Calculator: TServiceCalculator;
begin
Calculator := nil;
Calculator := TServiceCalculator.Create;
try
Calculator.FRefCount := 1;
result := Calculator.Add(a,b);
finally
dec(Calculator.FRefCount);
if Calculator.FRefCount=0 then
Calculator.Free;
end;
end;
Delphi and interfaces
Interfaces are orthogonal to implementation
There is more than one way to do it
From Interfaces to SOLID
type
TOtherServiceCalculator = class(TInterfacedObject, ICalculator)
protected
function Add(n1,n2: integer): integer;
end;
function TOtherServiceCalculator.Add(n1, n2: integer): integer;
begin
result := n2+n1;
end;
function MyOtherAdd(a,b: integer): integer;
var Calculator: ICalculator;
begin
ICalculator := TOtherServiceCalculator.Create;
result := Calculator.Add(a,b);
end;
SOLID design principles
Single responsibility principle
Open/closed principle
Liskov substitution principle (design by contract)
Interface segregation principle
Dependency inversion principle
From Interfaces to SOLID
SOLID design principles
Single responsibility
Object should have only a single responsibility
Open/closed
Entities should be open for extension,
but closed for modification
Liskov substitution (design by contract)
Objects should be replaceable with instances of their
subtypes without altering the correctness of that program
Interface segregation
Many specific interfaces are better than one
Dependency inversion
Depend upon abstractions, not depend upon concretions
From Interfaces to SOLID
SOLID design principles
Help to fight well-known weaknesses
Rigidity
Hard to change something because every change
affects too many other parts of the system
Fragility
When you make a change, unexpected parts of the
system break
Immobility
Hard to reuse in another application because it cannot
be disentangled from the current application
From Interfaces to SOLID
SOLID design principles
Single Responsibility
When you define a class, it shall be designed to
implement only one feature
The so-called feature can be seen as
an "axis of change" or a "a reason for change"
From Interfaces to SOLID
SOLID design principles
Single Responsibility
One class shall have only one reason
that justifies changing its implementation
Classes shall have few dependencies
on other classes
Classes shall be abstracted
from the particular layer they are running
From Interfaces to SOLID
SOLID design principles
Single Responsibility
Splitting classes
Imagine a TBarCodeScanner class
to handle a serial bar-code scanner
Later on, we want to implement USB support
First idea may be to inherit
From Interfaces to SOLID
SOLID design principles
Single Responsibility
Splitting classes
Imagine a TBarCodeScanner class
to handle a serial bar-code scanner
Later on, we want to implement USB support
But we would rather split the class hierarchy
From Interfaces to SOLID
SOLID design principles
Single Responsibility
Splitting classes
Later on, the manufacturer updates its protocol
To add new features, e.g. 3D scanning or coffee making
How do we implement this?
We have two “axis of change”
so we would define two classes
From Interfaces to SOLID
SOLID design principles
Single Responsibility
Splitting classes
Later on, the manufacturer updates its protocol
To add new features, e.g. 3D scanning or coffee making
We defined two classes, which will be joined/composed
in actual barcode scanner classes
From Interfaces to SOLID
SOLID design principles
Single Responsibility
Splitting classes
TAbstractBarcodeScanner = class(TComponent)
protected
fProtocol: TAbstractBarcodeProtocol;
fConnection: AbstractBarcodeConnection;
...
constructor TSerialBarCodeScanner.Create(
const aComPort: string; aBitRate: integer);
begin
fConnection := TSerialBarcodeConnection(aComPort,aBitRate);
fProtocol := TBCP1BarcodeProtocol.Create(fConnection);
end;
From Interfaces to SOLID
SOLID design principles
Single Responsibility
Another example
from SynDB class definitions:
TSQLDBConnectionProperties
TSQLDBConnection
TSQLDBStatement
Each class has its own purpose,
and axis of change
And could be implemented via ODBC, OleDB,
direct client access…
From Interfaces to SOLID
SOLID design principles
Single Responsibility
Do not mix GUI and logic
Do not mix logic and database
Do not couple your code to an OS
Check your uses statement
There should be no reference to the UI
(e.g. Dialogs) in your business class
No dependency to DB libraries (this is a hard one)
No reference to WinAPI.Windows
From Interfaces to SOLID
SOLID design principles
Single Responsibility
Code smells
When abusing of:
FreeAndNil()
{$ifdef} … {$endif}
uses unit1, unit2, unit3, … unit1000;
When you can’t find the right method in a class
(mORMot may need some refactoring here)
When unitary tests are hard to write
From Interfaces to SOLID
SOLID design principles
Open / Close principle
When you define a class or an interface
it shall be open for extension
but closed for modification
From Interfaces to SOLID
SOLID design principles
Open / Closed principle
Open for extension
Abstract class is overridden by implementations
No singleton nor global variable – ever
Rely on abstraction
if aObject is aClass then … it stinks!
Closed for modification
E.g. via explicitly protected or private members
RTTI is dangerous: it may open the closed door
From Interfaces to SOLID
SOLID design principles
Open / Closed principle
In practice
Define meaningful interface types
Following the Design By Contract principle
Define Value objects (DTOs) to transfer the data
With almost no methods, but for initialization
With public members, to access the value
Define Process objects to modify the data
With public methods, abstracted in the interface
With mostly private members
From Interfaces to SOLID
SOLID design principles
Open / Close principle
type
TAbstractBarcodeScanner = class(TComponent)
protected
fProtocol: TAbstractBarcodeProtocol;
fConnection: AbstractBarcodeConnection;
public
property Protocol: TAbstractBarcodeProtocol read fProtocol;
property Connection: AbstractBarcodeConnection read fConnection;
...
Protocol and Connection are read/only
They are injected by the overridden class constructor
So it will be Open for extension
From Interfaces to SOLID
SOLID design principles
Open / Close principle
type
TAbstractBarcodeScanner = class(TComponent)
protected
fProtocol: TAbstractBarcodeProtocol;
fConnection: AbstractBarcodeConnection;
public
property Protocol: TAbstractBarcodeProtocol read fProtocol;
property Connection: AbstractBarcodeConnection read fConnection;
...
Protocol and Connection are read/only
You could not change the behavior of a class
It is Closed for modification
From Interfaces to SOLID
SOLID design principles
Liskov substitution principle
If TChild is a subtype of TParent
then objects of type TParent
may be replaced with objects of type TChild
without altering any of the desirable properties
of that program (correctness, task performed, etc.)
From Interfaces to SOLID
SOLID design principles
Liskov substitution principle
Tied to the Open / Closed principle
If you define a child, you should not modify the parent
Code-reusability of the parent implementation
You will be able
to stub or mock an interface or a class
Allow correct testing of a whole system:
even if all single unit tests did pass,
real system may not work if this principle was broken
From Interfaces to SOLID
SOLID design principles
Liskov substitution patterns
Design by contract
Meyer's (Eiffel language) rule:
“when redefining a routine [in a derivative],
you may only replace its precondition by a weaker
one, and its postcondition by a stronger one”
Define assertions at method level:
What does it expect, guarantee, and maintain?
About input/output values or types, side effects,
invariants, errors/exceptions, performance…
From Interfaces to SOLID
SOLID design principles
Liskov substitution patterns
Write your code using abstract variables
Rely on parents methods and properties
Use interface variables
instead of class implementation
Uncouple dependencies via class composition
From Interfaces to SOLID
SOLID design principles
Liskov substitution patterns
Factory pattern
In strongly typed languages (Java, C#, Delphi),
the factory is the class/object type and its constructor
Repository pattern
Allow persistence agnosticism (e.g. via ORM)
Service locator pattern
Get a class instance implementing a given interface
According to the given context (no global/singleton)
From Interfaces to SOLID
SOLID design principles
Liskov substitution patterns
Practical, not dogmatic LSP
“Parent” and “Child” are not absolute
Depending on the context, you may define a given level
as the “highest” LSP abstract class
e.g. if you work on server side, you may need some
server-only properties and methods
LSP idea is to be consistent,
once the abstraction level is defined
From Interfaces to SOLID
SOLID design principles
Liskov substitution patterns
Practical, not dogmatic LSP
“Parent” and “Child” are not absolute
Inheritance is often used as code sharing among classes,
not as an abstraction contract
In this context, LSP may not apply at class level:
be pragmatic, and write efficient code
But LSP may apply at interface level,
for a set of methods implemented by the class
From Interfaces to SOLID
SOLID design principles
Liskov substitution code smells
if aObject is aClass then …
case aObject.EnumeratedType of …
function … abstract;
without further override;
unit parent;
uses child1,child2,child3;
From Interfaces to SOLID
SOLID design principles
Interface segregation principle
Once an interface has become too 'fat'
it shall be split into smaller
and more specific interfaces
so that any clients of the interface will only know
about the methods that pertain to them
In a nutshell, no client should be forced
to depend on methods it does not use
From Interfaces to SOLID
SOLID design principles
Interface segregation principle
Smaller dedicated classes should be preferred
Single Responsibility Principle
Favor composition over inheritance
Smaller dedicated interfaces
Gather operations/methods per context
Meaningful naming and conventions
So that the contract would be easier to understand
From Interfaces to SOLID
SOLID design principles
Interface segregation principle
Perfectly fits the SOA uncoupling pattern
Stateless MicroServices for horizontal scaling
Allows to release memory and resources ASAP
Smaller objects have more bounded dependencies
Ease unit testing
Less coverage
Less coupling
From Interfaces to SOLID
SOLID design principles
Interface segregation principle
Excludes RAD
Logic implemented in TForm TDataModule
where methods are procedural code in disguise
Put your logic behind interfaces
and call them from your UI (over VCL and FMX)
Favors DDD
Segregation avoid domain leaking
Dedicated interfaces, e.g. for third party consumption
From Interfaces to SOLID
SOLID design principles
Dependency Inversion
High-level modules
should not depend on low-level modules
Both should depend on abstractions
Following other SOLI principles
Abstractions should not depend upon details
Details should depend upon abstractions
Business Logic should not depend on database
Application Layer should not depend on client UI techno
From Interfaces to SOLID
SOLID design principles
Dependency Inversion
In most conventional programming style:
You write low-level components (e.g. DB tables)
Then you integrate them with high-level components
But this limits the re-use of high-level code
In fact, it breaks the Liskov substitution principle
It reduces the testing abilities (e.g. need of a real DB)
From Interfaces to SOLID
SOLID design principles
Dependency Inversion may be implemented
Via a plug-in system
e.g. external libraries (for embedded applications)
Using a service locator
e.g. SOA catalog (SaaS/cloud)
class resolution from an interface type
From Interfaces to SOLID
SOLID design principles
Dependency Inversion may be implemented
Via Dependency Injection
Concretions are injected to the objects using them
Only abstractions are known at design/coding time
Uncoupled implementation injected at runtime
Modular coding
Perfect for bigger/complex projects, with a set of teams
Ease testing, via stub/mock dependencies injection
e.g. a fake database, defined as a Repository service
Scaling abilities
From Interfaces to SOLID
SOLID design principles
Dependency Inversion may be implemented
Via Dependency Injection
A class will define its dependencies as read-only
interface members
Implementation will be injected at constructor level
via explicit constructor parameters
via automated resolution via RTTI
via service locator/resolver
From Interfaces to SOLID
DI, Stubs and Mocks
Thanks to SOLID design principles
All your code logic will now be abstracted
to the implementation underneath
But you need to inject the implementation
This is Dependency Injection purpose
You can also create fake instances to implement
a given interface, and enhance testing
Introducing Stubs and Mocks
From Interfaces to SOLID
DI, Stubs and Mocks
Dependency Injection
Define external dependencies as interface
as (private / protected) read-only members
To set the implementation instance:
Either inject the interfaces as constructor parameters
Or use a Factory / Service locator
From Interfaces to SOLID
DI, Stubs and Mocks
Dependency Injection
Purpose is to test the following class:
From Interfaces to SOLID
TLoginController = class(TInterfacedObject,ILoginController)
protected
fUserRepository: IUserRepository;
fSmsSender: ISmsSender;
public
constructor Create(const aUserRepository: IUserRepository;
const aSmsSender: ISmsSender);
procedure ForgotMyPassword(const UserName: RawUTF8);
end;
constructor TLoginController.Create(const aUserRepository: IUserRepository;
const aSmsSender: ISmsSender);
begin
fUserRepository := aUserRepository;
fSmsSender := aSmsSender;
end;
DI, Stubs and Mocks
Dependency Injection
Dependencies are defined as
Two small, uncoupled, SOLID task-specific interfaces
From Interfaces to SOLID
IUserRepository = interface(IInvokable)
['{B21E5B21-28F4-4874-8446-BD0B06DAA07F}']
function GetUserByName(const Name: RawUTF8): TUser;
procedure Save(const User: TUser);
end;
ISmsSender = interface(IInvokable)
['{8F87CB56-5E2F-437E-B2E6-B3020835DC61}']
function Send(const Text, Number: RawUTF8): boolean;
end;
DI, Stubs and Mocks
Dependency Injection
Using a dedicated Data Transfer Object (DTO)
No dependency against storage, nor other classes
From Interfaces to SOLID
IUserRepository = interface(IInvokable)
['{B21E5B21-28F4-4874-8446-BD0B06DAA07F}']
function GetUserByName(const Name: RawUTF8): TUser;
procedure Save(const User: TUser);
end;
TUser = record
Name: RawUTF8;
Password: RawUTF8;
MobilePhoneNumber: RawUTF8;
ID: Integer;
end;
DI, Stubs and Mocks
Dependency Injection
The high-level method to be tested:
Open/Closed, Liskov and mainly Dependency
Inversion principles are followed
Will we need a full database and to send a SMS?
From Interfaces to SOLID
procedure TLoginController.ForgotMyPassword(const UserName: RawUTF8);
var U: TUser;
begin
U := fUserRepository.GetUserByName(UserName);
U.Password := Int32ToUtf8(Random(MaxInt));
if fSmsSender.Send('Your new password is '+U.Password,U.MobilePhoneNumber) then
fUserRepository.Save(U);
end;
DI, Stubs and Mocks
"The Art of Unit Testing" (Osherove, Roy - 2009)
Stubs are fake objects
implementing a given contract
and returning pre-arranged responses
They just let the test pass
They “emulate” some behavior (e.g. a database)
From Interfaces to SOLID
DI, Stubs and Mocks
"The Art of Unit Testing" (Osherove, Roy - 2009)
Mocks are fake objects like stubs
which will verify if an interaction occurred or not
They help decide if a test failed or passed
There should be only one mock per test
From Interfaces to SOLID
DI, Stubs and Mocks
Expect – Run – Verify pattern
From Interfaces to SOLID
procedure TMyTest.ForgotMyPassword;
var SmsSender: ISmsSender;
UserRepository: IUserRepository;
begin
TInterfaceStub.Create(ISmsSender,SmsSender).
Returns('Send',[true]);
TInterfaceMock.Create(IUserRepository,UserRepository,self).
ExpectsCount('Save',qoEqualTo,1);
with TLoginController.Create(UserRepository,SmsSender) do
try
ForgotMyPassword('toto');
finally
Free;
end;
end;
DI, Stubs and Mocks
Expect – Run – Verify pattern
TInterfaceStub / TInterfaceMock constructors
are in fact Factories for any interface
Clear distinction between stub and mock
Mock is linked to its test case (self: TMyTest)
From Interfaces to SOLID
procedure TMyTest.ForgotMyPassword;
var SmsSender: ISmsSender;
UserRepository: IUserRepository;
begin
TInterfaceStub.Create(ISmsSender,SmsSender).
Returns('Send',[true]);
TInterfaceMock.Create(IUserRepository,UserRepository,self).
ExpectsCount('Save',qoEqualTo,1);
DI, Stubs and Mocks
Expect – Run – Verify pattern
Execution code itself sounds like real-life code
But all dependencies have been injected
Stubs will emulate real behavior
Mock will verify that all expectations are fulfilled
From Interfaces to SOLID
with TLoginController.Create(UserRepository,SmsSender) do
try
ForgotMyPassword('toto');
finally
Free;
end;
end;
DI, Stubs and Mocks
Expect – Run – Verify pattern
From Interfaces to SOLID
procedure TMyTest.ForgotMyPassword;
var SmsSender: ISmsSender;
UserRepository: IUserRepository;
begin
TInterfaceStub.Create(ISmsSender,SmsSender).
Returns('Send',[true]);
TInterfaceMock.Create(IUserRepository,UserRepository,self).
ExpectsCount('Save',qoEqualTo,1);
with TLoginController.Create(UserRepository,SmsSender) do
try
ForgotMyPassword('toto');
finally
Free;
end;
end;
DI, Stubs and Mocks
Run – Verify (aka “Test spy”) pattern
From Interfaces to SOLID
procedure TMyTest.ForgotMyPassword;
var SmsSender: ISmsSender;
UserRepository: IUserRepository;
Spy: TInterfaceMockSpy;
begin
TInterfaceStub.Create(ISmsSender,SmsSender).
Returns('Send',[true]);
Spy := TInterfaceMockSpy.Create(IUserRepository,UserRepository,self);
with TLoginController.Create(UserRepository,SmsSender) do
try
ForgotMyPassword('toto');
finally
Free;
end;
Spy.Verify('Save');
end;
DI, Stubs and Mocks
Another features:
Return complex values (e.g. a DTO)
Use a delegate to create a stub/mock
Using named or indexed variant parameters
Using JSON array of values
Access the test case when mocking
Trace and verify the calls
With a fluent interface
Log all calls (as JSON)
From Interfaces to SOLID
DI, Stubs and Mocks
Dependency Injection
Inheriting from TInjectableObject
type
TServiceToBeTested = class(TInjectableObject,IServiceToBeTested)
protected
fService: IInjectedService;
published
property Service: IInjectedService read fService;
end;
Will auto-inject interface published properties
At instance creation
Handled by TSQLRest.Services
From Interfaces to SOLID
DI, Stubs and Mocks
Dependency Injection
Inheriting from TInjectableObject
var Test: IServiceToBeTested;
begin
Test := TServiceToBeTested.CreateInjected(
[ICalculator],
[TInterfaceMock.Create(IPersistence,self).
ExpectsCount('SaveItem',qoEqualTo,1),
RestInstance.Services],
[AnyInterfacedObject]);
...
From Interfaces to SOLID
DI, Stubs and Mocks
Dependency Injection
Inheriting from TInjectableObject
procedure TServiceToBeTested.AnyProcessMethod;
var Service: IInjectedService;
begin
Resolve(IInjectedService,Service);
Service.DoSomething;
end;
From Interfaces to SOLID
DI, Stubs and Mocks
Dependency Injection
Inheriting from TInjectableAutoCreateFields
type
TServiceToBeTested = class(TInjectableObjectAutoCreateFields,
IServiceToBeTested)
protected
fService: IInjectedService;
fNestedObject: TSynPersistentValue;
published
property Service: IInjectedService read fService;
property NestedObject: TSynPersistentValue read fNestedObject;
end;
Will auto-define published properties
Resolve interface services
Create TPersistent TSynPersistent TAutoCreateField
From Interfaces to SOLID
Weak references
Delphi type reference model
class
as weak references (plain pointer) and explicit Free
with TComponent ownership for the VCL/FMX
integer Int64 currency double record
widestring variant
with explicit copy
string or any dynamic array
via copy-on-write (COW) with reference counting
interface
as strong reference with reference counting
From Interfaces to SOLID
Weak references
Strong reference-counted types (OLE/ COM)
Will increase the count at assignment
And decrease the count at owner’s release
When the count reaches 0, release the instance
Issue comes when there are
Circular references
External list(s) of references
From Interfaces to SOLID
Weak references
Managed languages (C# or Java)
Will let the Garbage Collector handle
interface variable life time
This is complex and resource consuming
But easy to work with
Unmanaged languages (Delphi or ObjectiveC)
Need explicit weak reference behavior
mORMot features zeroing weak pointers
Like Apple’s ARC model
From Interfaces to SOLID
Weak references
Zeroing weak pointers
From Interfaces to SOLID
IParent = interface
procedure SetChild(const Value: IChild);
function GetChild: IChild;
function HasChild: boolean;
property Child: IChild read GetChild write SetChild;
end;
IChild = interface
procedure SetParent(const Value: IParent);
function GetParent: IParent;
property Parent: IParent read GetParent write SetParent;
end;
Weak references
Zeroing weak pointers
This code will leak memory
From Interfaces to SOLID
IParent = interface
procedure SetChild(const Value: IChild);
function GetChild: IChild;
function HasChild: boolean;
property Child: IChild read GetChild write SetChild;
end;
IChild = interface
procedure SetParent(const Value: IParent);
function GetParent: IParent;
property Parent: IParent read GetParent write SetParent;
end;
procedure TParent.SetChild(const Value: IChild);
begin
FChild := Value;
end;
procedure TChild.SetParent(const Value: IParent);
begin
FParent := Value;
end;
Weak references
Zeroing weak pointers
This code won’t leak memory
FChild and FParent will be set to nil
when the stored instance will be freed
From Interfaces to SOLID
IParent = interface
procedure SetChild(const Value: IChild);
function GetChild: IChild;
function HasChild: boolean;
property Child: IChild read GetChild write SetChild;
end;
IChild = interface
procedure SetParent(const Value: IParent);
function GetParent: IParent;
property Parent: IParent read GetParent write SetParent;
end;
procedure TParent.SetChild(const Value: IChild);
begin
SetWeakZero(self,@FChild,Value);
end;
procedure TChild.SetParent(const Value: IParent);
begin
SetWeakZero(self,@FParent,Value);
end;
Weak references
Delphi NextGen memory model
Uses ARC for every TObject instance
This is transparent for TComponent / FMX
No try … finally Free block needed
But breaks the proven weak reference model
and a lot of existing code
Only UTF-16 string type
Direct 8 bit string type disabled
UTF8String RawByteString back in 10.1 Berlin
From Interfaces to SOLID