Synopse Open Source - Tag - SynopsemORMot MVC / SOA / ORM and friends2024-02-02T17:08:25+00:00urn:md5:cc547126eb580a9adbec2349d7c65274DotclearmORMot on GitHub Sponsorsurn:md5:217d5e1b82e0e6e4b4f1bf3c8796a8682020-07-02T18:59:00+01:002021-02-22T08:41:11+00:00Arnaud BouchezSynopse CompanybloggithubsponsorSynopse<p>We just enrolled on <a href="https://github.com/sponsors/synopse">GitHub Sponsors</a>!</p>
<p><a href="https://github.com/sponsors/synopse"><img src="https://github.blog/wp-content/uploads/2019/05/mona-heart-featured.png?fit=240%2C126" alt="" /></a></p>
<p>Aim is to allow proper evolution of our Open Source project, especially the upcoming <em>mORMot2</em>.</p> <p>We propose several options, from monthly 5$ for "thank you", up to monthly support with NDA for companies.</p>
<p>This may be a good opportunity to <a href="https://github.com/sponsors/synopse">help our projects,</a> if you are willing to!</p>Preparing Revision 2.x of the mORMot Frameworkurn:md5:a69f3101f568a4660f5bc36e69d1adc42020-03-03T20:14:00+01:002020-03-06T14:54:33+01:00AB4327-GANDImORMot FrameworkblogDelphiFreePascalGoodPracticemORMotmORMot2RepositoryRESTSourceSQLite3Synopse<p>The more I think of it, the more I am convinced it is time to change how the
framework is versioned.<br />
We have version 1.18 since years... difficult to follow... time to upgrade!</p>
<p><img src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSsiYRtu9W1D-p2scisYFSumx8FPoJlk5cHU0cmy-en6XpEgzPXMg&s" alt="" /><br />
I would like to upgrade <em>mORMot</em> to version 2 - with a major
refactoring.</p> <p><br class="Apple-interchange-newline" />
So, in practice, two main points to discuss:</p>
<p>1. Switch to <a href="https://semver.org">Semantic Versioning</a></p>
<p><em>Given a version number MAJOR.MINOR.PATCH, increment the:<br />
- MAJOR version when you make incompatible API changes,<br />
- MINOR version when you add functionality in a backwards compatible manner,
and<br />
- PATCH version when you make backwards compatible bug fixes.<br />
Additional labels for pre-release and build metadata are available as
extensions to the MAJOR.MINOR.PATCH format.</em></p>
<p>The idea would be to have 2.# minor version every month, with patches
in-between. A monthly minor upgrade allow to have new features included quick
enough to be interresting.</p>
<p>2. <em>mORMot</em> 2.0.0 would induce incompatible API changes.</p>
<p>I would like:</p>
<p>2.1. To remove all reminicence of the "SQLite3 framework", e.g. the SQLite3
sub-folder.</p>
<p>2.2. Try to split <em>SynCommons.pas</em> and <em>mORMot.pas</em> units into
smaller units. Putting e.g. the raw JSON process or RTTI in some external
unit.</p>
<p>2.3. Introduce some high-level classes and units, perhaps as (why-not?) some
non-visual <em>TComponent</em> version of our classes. Keeping existing
low-level classes, which are stable and full of features - but difficult to
adopt.</p>
<p>2.4. Get rid of oldest Delphi (e.g. Delphi 5 - which is currently broken
BTW) official compatibility, only support mainstream Delphi revisions, and
focus on FPC trunk as main target -and officially maintaining only a few
version of Delphi (2007/XE7/10.3?) - with other version maintained by
contributors.</p>
<p>Any feedback is <a href="https://synopse.info/forum/viewtopic.php?id=5313">welcome in our forum, as
usual</a>!</p>Support of Delphi 10.1 Berlinurn:md5:e51dd652892edaaf7cb9d90bd85f55a52016-04-22T11:29:00+02:002016-04-22T11:29:00+02:00AB4327-GANDIOpen SourceblogDelphiDocumentationEmbarcaderomORMotOpenSourceSynopse <p>You should have noticed that <a href="http://docwiki.embarcadero.com/RADStudio/Berlin/en/What's_New">Delphi 10.1
Berlin</a> has been released.</p>
<p><img src="http://www.blong.com/images/Eggs/Delphi1a.gif" alt="" /></p>
<p>Our <a href="http://synopse.info/fossil/wiki/Synopse+OpenSource">Open Source
projects</a>, including <em>mORMot</em> and <em>SynPDF</em> and their
<a href="http://synopse.info/files/html/Synopse%20mORMot%20Framework%20SAD%201.18.html">
associated documentation</a> have been updated to support this new
revision.<br />
Any <a href="http://synopse.info/forum/viewtopic.php?id=3280">additional
feedback is welcome</a>, as usual!</p>Asynchronous Service - WebSockets, Callbacks and Publish-Subscribeurn:md5:4b0f2fbc0b9085026d4baacbf5ee8ed92015-04-06T21:34:00+02:002015-04-06T20:57:33+02:00AB4327-GANDImORMot FrameworkAESAES-NiAJAXauthenticationblogDatabaseDelphiDocumentationEventCollaborationEventSourcinginterfaceModelORMsecuritySOASOLIDSourceSQLite3SynopseWebSockets<p>When publishing SOA services, most of them are defined as
<em>stateless</em>, in a typical query/answer pattern - see <em><a href="http://synopse.info/files/html/Synopse%20mORMot%20Framework%20SAD%201.18.html#TITL_17">
Service-Oriented Architecture (SOA)</a></em>.<br />
This fits exactly with the <em>RESTful</em> approach of <em>Client-Server
services via interfaces</em>, as proposed by the framework.</p>
<p>But it may happen that a client application (or service) needs to know the
state of a given service. In a pure <em>stateless</em> implementation, it will
have to <em>query</em> the server for any state change, i.e. for any pending
notification - this is called <em>polling</em>.</p>
<p><img src="https://blog.synopse.info?post/public/mORMot/BatNotification.png" alt="" title="RealTime Notification, Apr 2015" /></p>
<p><em>Polling</em> may take place for instance:</p>
<ul>
<li>When a time consuming work is to be processed on the server side. In this
case, the client could not wait for it to be finished, without raising a
timeout on the HTTP connection: as a workaround, the client may start the work,
then ask for its progress status regularly using a timer and a dedicated method
call;</li>
<li>When an unpredictable event is to be notified from the server side. In this
case, the client should ask regularly (using a timer, e.g. every second), for
any pending event, then react on purpose.</li>
</ul>
<p>It may therefore sounds preferred, and in some case necessary, to have the
ability to let the server <em>notify</em> one or several clients without any
prior query, nor having the requirement of a client-side timer:</p>
<ul>
<li><em>Polling</em> may be pretty resource consuming on both client and server
sides, and add some unwanted latency;</li>
<li>If immediate notification is needed, some kind of "long polling" algorithm
may take place, i.e. the server will wait for a long time before returning the
notification state if no event did happen: in this case, a dedicated connection
is required, in addition to the REST one;</li>
<li>In an event-driven systems, a lot of messages are sent to the clients: a
proper publish/subscribe mechanism is preferred, otherwise the complexity of
polling methods may increase and become inefficient and unmaintainable;</li>
<li>Explicit push notifications may be necessary, e.g. when a lot of potential
events, associated with a complex set of parameters, are likely to be sent by
the client.</li>
</ul>
<p>Our <em>mORMot</em> framework is therefore able to easily implement
asynchronous callbacks over <em><a href="http://www.developerfusion.com/article/143158/an-introduction-to-websockets/">WebSockets</a></em>,
defining the callbacks as <code>interface</code> parameters in service method
definitions - see <em><a href="http://synopse.info/files/html/Synopse%20mORMot%20Framework%20SAD%201.18.html#TITL_154">
Available types for methods parameters</a></em>.</p> <h3>1. WebSockets support</h3>
<p>By definition, HTTP connections are stateless and one-way, i.e. a client
sends a request to the server, which replies back with an answer.<br />
There is no way to let the server send a message to the client, without a prior
request from the client side.</p>
<p><em>WebSockets</em> is a communication protocol which is able to
<em>upgrade</em> a regular HTTP connection into a dual-way communication
wire.<br />
After a safe handshake, the underlying TCP/IP socket is able to be accessed
directly, via a set of lightweight <em>frames</em> over an application-defined
<em>protocol</em>, without the HTTP overhead.</p>
<p>The <code><a href="http://synopse.info/files/html/api-1.18/SynBidirSock.html">SynBidirSock.pas</a></code>
unit implements low-level server and client <em>WebSockets</em>
communication.</p>
<p>The <code>TWebSocketProtocol</code> class defines an abstract <em>WebSockets
protocol</em>, currently implemented as several classes.</p>
<p>For our <em><a href="http://synopse.info/files/html/Synopse%20mORMot%20Framework%20SAD%201.18.html#SIDE_TITL_63">
Client-Server services via interfaces</a></em>, we would still need to make
<em>RESTful</em> requests, so the basic <em>WebSockets</em> framing has been
enhanced to support <code>TWebSocketProtocolRest</code> REST-compatible
protocols, able to use the single connection for both REST queries and
asynchronous notifications.</p>
<p>Two classes are available for your SOA applications:</p>
<ul>
<li><code>TWebSocketProtocolJSON</code> as a "pure" JSON light protocol;</li>
<li><code>TWebSocketProtocolBinary</code> as a binary proprietary protocol,
with optional frame compression and AES encryption (using AES-NI hardware
instructions, if available).</li>
</ul>
<p>In practice, on the server side, you would start your
<code>TSQLHttpServer</code> by specifying <code>useBidirSocket</code> as kind
of server:</p>
<pre>
HttpServer := TSQLHttpServer.Create('8888',[Server],'+',useBidirSocket);
</pre>
<p>Under the hood, it will instantiate a <code>TWebSocketServer</code> HTTP
server, as defined in <code>mORMotHttpServer.pas</code>, based on the sockets
API, able to upgrade the HTTP protocol into <em>WebSockets</em>.<br />
Our <em>High-performance http.sys server</em> is not yet able to switch to
<em>WebSockets</em> - and at API level, it would require at least <em>Windows
8</em> or <em>Windows 2012 Server</em>.</p>
<p>Then you enable <em>WebSockets</em> for the
<code>TWebSocketProtocolBinary</code> protocol, with an encryption key:</p>
<pre>
HttpServer.WebSocketsEnable(Server,'encryptionkey');
</pre>
<p>On the client side, you would use a <code>TSQLHttpClientWebsockets</code>
instance, as defined in <code>mORMotHttpClient.pas</code>, then explicitly
upgrade the connection to use <em>WebSockets</em> (since by default, it will
stick to the HTTP protocol):</p>
<pre>
Client := TSQLHttpClientWebsockets.Create('127.0.0.1','8888',TSQLModel.Create([]));
Client.WebSocketsUpgrade('encryptionkey');
</pre>
<p>The expected protocol detail should match the one on the server, i.e.
<code>'encryptionkey'</code> encryption over our binary protocol.</p>
<p>Once upgraded to <em>WebSockets</em>, you may use regular REST commands, as
usual:</p>
<pre>
Client.ServerTimeStampSynchronize;
</pre>
<p>But in addition to regular query/answer commands as defined for
<em>Client-Server services via interfaces</em>, you would be able to define
callbacks using <code>interface</code> parameters to the service methods.</p>
<p>Under the hood, both client and server will communicate using
<em>WebSockets</em> frames, maintaining the connection active using heartbeats
(via ping/pong frames), and with clean connection shutdown, from any side. You
can use the <code>Settings</code> property of the
<code>TWebSocketServerRest</code> instance, as returned by
<code>TSQLHttpServer.WebSocketsEnable()</code>, to customize the low-level
<em>WebSockets</em> protocol (e.g. timeouts or heartbeats) on the server side.
The <code>TSQLHttpClientWebsockets.WebSockets</code>.<code>Settings</code>
property would allow the same, on the client side.</p>
<p>We have observed, from our regression tests and internal benchmarking, that
using our <em>WebSockets</em> may be faster than regular HTTP, since its frames
would be sent as once, whereas HTTP headers and body are not sent in the same
TCP packet, and compression would be available for the whole frame, whereas
HTTP headers are not compressed. The ability to use strong AES encryption would
make this mean of communication even safer than plain HTTP, even with
<em><a href="http://synopse.info/files/html/Synopse%20mORMot%20Framework%20SAD%201.18.html#TITL_151">
AES encryption over HTTP</a></em>.</p>
<h3>2. Using a callback to notify long term end-of-process</h3>
<p>An example is better than 100 talks.<br />
So let's take a look at the <code>Project31LongWorkServer.dpr</code> and
<code>Project31LongWorkClient.dpr</code> samples, from the <code><a href="https://github.com/synopse/mORMot/tree/master/SQLite3/Samples/31%20-%20WebSockets">
SQLite3\Samples\31 - WebSockets</a></code> sub-folder.<br />
They will implement a client/server application, in which the client launches a
long term process on the server side, then is notified when the process is
done, either with success, or failure.</p>
<p>First we define the interfaces to be used, in a shared
<code>Project31LongWorkCallbackInterface.pas</code> unit:</p>
<pre>
<strong>type</strong>
ILongWorkCallback = <strong>interface</strong>(IInvokable)
['{425BF199-19C7-4B2B-B1A4-A5BE7A9A4748}']
<strong>procedure</strong> WorkFinished(<strong>const</strong> workName: <strong>string</strong>; timeTaken: integer);
<strong>procedure</strong> WorkFailed(<strong>const</strong> workName, error: <strong>string</strong>);
<strong>end</strong>;<br /> ILongWorkService = <strong>interface</strong>(IInvokable)
['{09FDFCEF-86E5-4077-80D8-661801A9224A}']
<strong>procedure</strong> StartWork(<strong>const</strong> workName: <strong>string</strong>; <strong>const</strong> onFinish: ILongWorkCallback);
<strong>function</strong> TotalWorkCount: Integer;
<strong>end</strong>;
</pre>
<p>The only specific definition is the <code>const onFinish:
ILongWorkCallback</code> parameter, supplied to the
<code>ILongWorkService.StartWork()</code> method.<br />
The client will create a class implementing <code>ILongWorkCallback</code>,
then specify it as parameter to this method.<br />
On the server side, a "fake" class will implement
<code>ILongWorkCallback</code>, then will call back the client using the very
same <em>WebSockets</em> connection, when any of its methods will be
executed.</p>
<p>As you can see, a single callback <code>interface</code> instance may have
several methods, with their own set of parameters (here
<code>WorkFinished</code> and <code>WorkFailed</code>), so that the callback
may be quite expressive.<br />
Any kind of usual parameters would be transmitted, after serialization:
<code>string</code>, <code>integer</code>, but even <code>record</code>,
<em>dynamic arrays</em>, <code>TSQLRecord</code> or <code>TPersistent</code>
values.</p>
<p>When the <code>ILongWorkCallback</code> instance will be released on the
client side, the server will be notified, so that any further notification
won't create a connection error.<br />
We will see later how to handle those events.</p>
<h3>Client service consumption</h3>
<p>The client may be connected to the server as such (see the
<code>Project31LongWorkClient.dpr</code> sample source code for the full
details, including error handling):</p>
<pre>
<strong>var</strong> Client: TSQLHttpClientWebsockets;
workName: <strong>string</strong>;
Service: ILongWorkService;
callback: ILongWorkCallback;
<strong>begin</strong>
Client := TSQLHttpClientWebsockets.Create('127.0.0.1','8888',TSQLModel.Create([]));
<span style="background-color:yellow;">Client.WebSocketsUpgrade(PROJECT31_TRANSMISSION_KEY);</span>
Client.ServiceDefine([ILongWorkService],sicShared);
Client.Services.Resolve(ILongWorkService,Service);
</pre>
<p>Then we define our callback, using a dedicated class:</p>
<pre>
<strong>type</strong>
TLongWorkCallback = <strong>class</strong>(TInterfacedCallback,ILongWorkCallback)
<strong>protected</strong>
<strong>procedure</strong> WorkFinished(<strong>const</strong> workName: <strong>string</strong>; timeTaken: integer);
<strong>procedure</strong> WorkFailed(<strong>const</strong> workName, error: <strong>string</strong>);
<strong>end</strong>;<br /><strong>procedure</strong> TLongWorkCallback.WorkFailed(<strong>const</strong> workName, error: <strong>string</strong>);
<strong>begin</strong>
writeln(#13'Received callback WorkFailed(',workName,') with message "',error,'"');
<strong>end</strong>;
<br /><strong>procedure</strong> TLongWorkCallback.WorkFinished(<strong>const</strong> workName: <strong>string</strong>;
timeTaken: integer);
<strong>begin</strong>
writeln(#13'Received callback WorkFinished(',workName,') in ',timeTaken,'ms');
<strong>end</strong>;
</pre>
<p>Then we specify this kind of callback as parameter to start a long term
work:</p>
<pre>
<span style="background-color:yellow;">callback := TLongWorkCallback.Create(Client,ILongWorkCallback);</span>
<strong>try</strong>
<strong>repeat</strong>
readln(workName);
<strong>if</strong> workName='' <strong>then</strong>
break;
<span style="background-color:yellow;">Service.StartWork(workName,callback);</span>
<strong>until</strong> false;
<strong>finally</strong>
callback := <strong>nil</strong>; <em>// the server will be notified and release its "fake" class</em>
Service := <strong>nil</strong>; <em>// release the service local instance BEFORE Client.Free</em>
<strong>end</strong>;
</pre>
<p>As you can see, the client is able to start one or several work processes,
then expects to be notified of the process ending on its callback
<code>interface</code> instance, without explicitly polling the server for its
state, since the connection was upgraded to <em>WebSockets</em> via a call to
<code>TSQLHttpClientWebsockets.WebSocketsUpgrade()</code>.</p>
<h3>Server side implementation</h3>
<p>The server would define the working thread as such (see the
<code>Project31LongWorkServer.dpr</code> sample source code for the full
details):</p>
<pre>
<strong>type</strong>
TLongWorkServiceThread = <strong>class</strong>(TThread)
<strong>protected</strong>
<span style="background-color:yellow;">fCallback: ILongWorkCallback;</span>
fWorkName: <strong>string</strong>;
<strong>procedure</strong> Execute; <strong>override</strong>;
<strong>public</strong>
<strong>constructor</strong> Create(<strong>const</strong> workName: <strong>string</strong>; <strong>const</strong> callback: ILongWorkCallback);
<strong>end</strong>;
<br /><strong>constructor</strong> TLongWorkServiceThread.Create(<strong>const</strong> workName: <strong>string</strong>;
<strong>const</strong> callback: ILongWorkCallback);
<strong>begin</strong>
<strong>inherited</strong> Create(false);
<span style="background-color:yellow;">fCallback := Callback;</span>
fWorkName := workName;
FreeOnTerminate := true;
<strong>end</strong>;
<br /><strong>procedure</strong> TLongWorkServiceThread.Execute;
<strong>var</strong> tix: Int64;
<strong>begin</strong>
tix := GetTickCount64;
<span style="background-color:yellow;">Sleep(5000+Random(1000)); <em>// some hard work</em></span>
<strong>if</strong> Random(100)>20 <strong>then</strong>
<span style="background-color:yellow;">fCallback.WorkFinished(fWorkName,GetTickCount64-tix) <strong>else</strong></span>
<span style="background-color:yellow;">fCallback.WorkFailed(fWorkName,'expected random failure');</span>
<strong>end</strong>;
</pre>
<p>The callback is expected to be supplied as a <code>ILongWorkCallback</code>
interface instance, then stored in a <code>fCallback</code> protected field for
further notification.<br />
Some work is done in the <code>TLongWorkServiceThread.Execute</code> method
(here just a <code>Sleep()</code> of more than 5 seconds), and the end-of-work
notification is processed, as success or failure (depending on random in this
fake process class), on either of the <code>ILongWorkCallback</code> interface
methods.</p>
<p>The following <code>class</code> will define, implement and register the
<code>ILongWorkService</code> service on the server side:</p>
<pre>
<strong>type</strong>
TLongWorkService = <strong>class</strong>(TInterfacedObject,ILongWorkService)
<strong>protected</strong>
fTotalWorkCount: Integer;
<strong>public</strong>
<strong>procedure</strong> StartWork(<strong>const</strong> workName: <strong>string</strong>; <strong>const</strong> onFinish: ILongWorkCallback);
<strong>function</strong> TotalWorkCount: Integer;
<strong>end</strong>;
<br /><strong>procedure</strong> TLongWorkService.StartWork(<strong>const</strong> workName: <strong>string</strong>;
<strong>const</strong> onFinish: ILongWorkCallback);
<strong>begin</strong>
InterlockedIncrement(fTotalWorkCount);
<span style="background-color:yellow;">TLongWorkServiceThread.Create(workName,onFinish);</span>
<strong>end</strong>;
<br /><strong>function</strong> TLongWorkService.TotalWorkCount: Integer;
<strong>begin</strong>
result := fTotalWorkCount;
<strong>end</strong>;
<br /><strong>var</strong> HttpServer: TSQLHttpServer;
Server: TSQLRestServerFullMemory;
<strong>begin</strong>
Server := TSQLRestServerFullMemory.CreateWithOwnModel([]);
<span style="background-color:yellow;">Server.ServiceDefine(TLongWorkService,[ILongWorkService],sicShared);</span>
HttpServer := TSQLHttpServer.Create('8888',[Server],'+',useBidirSocket);
<span style="background-color:yellow;">HttpServer.WebSocketsEnable(Server,PROJECT31_TRANSMISSION_KEY);</span>
...
</pre>
<p>Purpose of those methods is just to create and launch the
<code>TLongWorkServiceThread</code> process from a client request, then
maintain a total count of started works, in a <code>sicShared</code> service
instance - see <em><a href="http://synopse.info/files/html/Synopse%20mORMot%20Framework%20SAD%201.18.html#TITL_92">
Instances life time implementation</a></em> - hosted in a
<code>useBidirSocket</code> kind of HTTP server.</p>
<p>We have to explicitly call <code>TSQLHttpServer.WebSocketsEnable()</code> so
that this server would be able to upgrade to our <em>WebSockets</em> protocol,
using our binary framing, and the very same encryption key as on the client
side - shared as a <code>PROJECT31_TRANSMISSION_KEY</code> constant in the
sample, but which may be safely stored on both sides.</p>
<h3>3. Publish-subscribe for events</h3>
<p>In event-driven architectures, the <em>publish-subscribe</em> messaging
pattern is a way of letting senders (called <em>publishers</em>) transmit
messages to their receivers (called <em>subscribers</em>), without any prior
knowledge of who those subscribers are.<br />
In practice, the <em>subscribers</em> will express interest for a set of
messages, which will be sent by the <em>publisher</em> to all the
<em>subscribers</em> of a given message, as soon as it is be notified.</p>
<p>In our <em>Client-Server services via interfaces</em> implementation,
messages are gathered in <code>interface</code> types, and each message defined
as a single method, their content being the methods parameters.<br />
Most of the SOA alternative (in Java or C#) do require class definition for
messages. Our KISS approach will just use method parameters values as message
definition.</p>
<p>To maintain a list of <em>subscribers</em>, the easiest is to store a
<em>dynamic array</em> of <code>interface</code> instances, on the
<em>publisher</em> side.</p>
<h3>Defining the interfaces</h3>
<p>We will now implement a simple <em>chat</em> service, able to let several
clients communicate together, broadcasting any message to all the other
connected instances.<br />
This sample is also located in the the <code><a href="https://github.com/synopse/mORMot/tree/master/SQLite3/Samples/31%20-%20WebSockets">
SQLite3\Samples\31 - WebSockets</a></code> sub-folder, as
<code>Project31ChatServer.dpr</code> and
<code>Project31ChatClient.dpr</code>.</p>
<p>So you first define the callback interface, and the service interface:</p>
<pre>
<strong>type</strong>
IChatCallback = <strong>interface</strong>(IInvokable)
['{EA7EFE51-3EBA-4047-A356-253374518D1D}']
<strong>procedure</strong> BlaBla(<strong>const</strong> pseudo, msg: <strong>string</strong>);
<strong>end</strong>;
<br /> IChatService = <strong>interface</strong>(IInvokable)
['{C92DCBEA-C680-40BD-8D9C-3E6F2ED9C9CF}']
<strong>procedure</strong> Join(<strong>const</strong> pseudo: <strong>string</strong>; <strong>const</strong> callback: IChatCallback);
<strong>procedure</strong> BlaBla(<strong>const</strong> pseudo,msg: <strong>string</strong>);
<strong>procedure</strong> CallbackReleased(<strong>const</strong> callback: IInvokable);
<strong>end</strong>;
</pre>
<p>Those interface types will be shared by both server and client sides, in the
common <code>Project31ChatCallbackInterface.pas</code> unit. The definition is
pretty close to what we wrote just above to notify long term
end-of-process.<br />
The only additional method is <code>IChatServer.CallbackReleased()</code>,
which, by convention, will be called on the server side when any
<code>callback</code> interface instance is released on the client side.</p>
<p>As such, the <code>IChatService.Join()</code> method will implement the
<em>subscription</em> to the chat service, whereas
<code>IChatServer.CallbackReleased()</code> will be called when the client-side
callback instance will be released (i.e. when its variable will be assigned to
<code>nil</code>), to <em>unsubscribe</em> for the chat service.</p>
<h3>Writing the Publisher</h3>
<p>On the server side, each call to <code>IChatService.Join()</code> would
<em>subscribe</em> to an internal list of connections, simply stored as an
<code>array of IChatCallback</code>:</p>
<pre>
<strong>type</strong>
TChatService = <strong>class</strong>(TInterfacedObject,IChatService)
<strong>protected</strong>
<span style="background-color:yellow;">fConnected: <strong>array of</strong> IChatCallback;</span>
<strong>public</strong>
<strong>procedure</strong> Join(<strong>const</strong> pseudo: <strong>string</strong>; <strong>const</strong> callback: IChatCallback);
<strong>procedure</strong> BlaBla(<strong>const</strong> pseudo,msg: <strong>string</strong>);
<strong>procedure</strong> CallbackReleased(<strong>const</strong> callback: IInvokable);
<strong>end</strong>;
<br /><strong>procedure</strong> TChatService.Join(<strong>const</strong> pseudo: <strong>string</strong>;
<strong>const</strong> callback: IChatCallback);
<strong>begin</strong>
<span style="background-color:yellow;">InterfaceArrayAdd(fConnected,callback);</span>
<strong>end</strong>;
</pre>
<p>The <code>InterfaceArrayAdd()</code> function, as defined in
<code>SynCommons.pas</code>, is a simple wrapper around any <em>dynamic
array</em> of <code>interface</code> instances, so that you may use it, or the
associated <code>InterfaceArrayFind()</code> or
<code>InterfaceArrayDelete()</code> functions, to maintain the list of
subscriptions.</p>
<p>Then a remote call to the <code>IChatService.BlaBla()</code> method should
be broadcasted to all connected clients, just by calling the
<code>IChatCallback.BlaBla()</code> method:</p>
<pre>
<strong>procedure</strong> TChatService.BlaBla(<strong>const</strong> pseudo,msg: <strong>string</strong>);
<strong>var</strong> i: integer;
<strong>begin</strong>
<strong>for</strong> i := 0 <strong>to</strong> high(fConnected) <strong>do</strong>
<span style="background-color:yellow;">fConnected[i].BlaBla(pseudo,msg);</span>
<strong>end</strong>;
</pre>
<p>Note that every call to <code>IChatCallback.BlaBla()</code> within the loop
would be made via <em>WebSockets</em>, in an asynchronous and non blocking way,
so that even in case of huge number of clients, the
<code>IChatService.BlaBla()</code> method won't block.<br />
In case of high numbers of messages, the framework is even able to
<em>gather</em> push notification messages into a single bigger message, to
reduce the resource use.</p>
<p>On the server side, the service implementation has been registered as
such:</p>
<pre>
Server.ServiceDefine(TChatService,[IChatService],sicShared).
SetOptions([],[optExecLockedPerInterface]);
</pre>
<p>Here, the <code>optExecLockedPerInterface</code> option has been set, so
that all method calls would be made thread-safe, so that concurrent access to
the internal <code>fConnected[]</code> list would be safe.<br />
Since a global list of connections is to be maintained, the service life time
is defined as <code>sicShared</code> - see <em><a href="http://synopse.info/files/html/Synopse%20mORMot%20Framework%20SAD%201.18.html#TITL_92">
Instances life time implementation</a></em>.</p>
<p>The following method will be called by the server, when a client callback
instance is released (either explicitly, or if the connection is broken), so
could be used to <em>unsubscribe</em> to the notification, simply by deleting
the callback from the internal <code>fConnected[]</code> array:</p>
<pre>
<strong>procedure</strong> TChatService.CallbackReleased(<strong>const</strong> callback: IInvokable);
<strong>begin</strong>
<span style="background-color:yellow;">InterfaceArrayDelete(fConnected,callback);</span>
<strong>end</strong>;
</pre>
<p>The framework will in fact recognize the following method definition in any
<code>interface</code> type for a service:</p>
<pre>
<strong>procedure</strong> CallbackReleased(<strong>const</strong> callback: IInvokable);
</pre>
<p>When a callback <code>interface</code> parameter (in our case,
<code>IChatCallback</code>) will be released on the client side, this method
will be called with the corresponding <code>interface</code> instance as
parameter.<br />
You do not have to call explicitly any method on the client side to
<em>unsubscribe</em> a service: assigning <em>nil</em> to a callback variable,
or feeing the <code>class</code> instance owning it as a field on the
<em>subscriber</em> side, will automatically unregister it on the
<em>publisher</em> side.</p>
<h3>Consuming the service from the Subscriber side</h3>
<p>On the client side, you implement the <code>IChatCallback</code> callback
interface:</p>
<pre>
<strong>type</strong>
TChatCallback = <strong>class</strong>(TInterfacedCallback,IChatCallback)
<strong>protected</strong>
<strong>procedure</strong> BlaBla(<strong>const</strong> pseudo, msg: <strong>string</strong>);
<strong>end</strong>;
<br /><strong>procedure</strong> TChatCallback.BlaBla(<strong>const</strong> pseudo, msg: <strong>string</strong>);
<strong>begin</strong>
writeln(#13'@',pseudo,' ',msg);
<strong>end</strong>;
</pre>
<p>Then you subscribe to your remote service as such:</p>
<pre>
<span style="background-color:yellow;"><strong>var</strong> Service: IChatService;</span>
<span style="background-color:yellow;">callback: IChatCallback;</span>
...
Client.ServiceDefine([IChatService],sicShared);
<span style="background-color:yellow;"><strong>if not</strong> Client.Services.Resolve(IChatService,Service) <strong>then</strong></span>
<strong>raise</strong> EServiceException.Create('Service IChatService unavailable');
...
<span style="background-color:yellow;">callback := TChatCallback.Create(Client,IChatCallback);</span>
<span style="background-color:yellow;">Service.Join(pseudo,callback);</span>
...
<strong>try</strong>
<strong>repeat</strong>
readln(msg);
<strong>if</strong> msg='' <strong>then</strong>
break;
<span style="background-color:yellow;">Service.BlaBla(pseudo,msg);</span>
<strong>until</strong> false;
<strong>finally</strong>
<span style="background-color:yellow;">callback := <strong>nil</strong>; <em>// will unsubscribe from the remote publisher</em></span>
Service := <strong>nil</strong>; <em>// release the service local instance BEFORE Client.Free</em>
<strong>end</strong>;
</pre>
<p>You could easily implement more complex <em>publish/subscribe</em>
mechanisms, including filtering, time to live or tuned broadcasting, by storing
some additional information to the <code>interface</code> instance (e.g. some
value to filter, a timestamp). A dynamic array of dedicated
<code>record</code>s, or a list of <code>class</code> instances, may be used to
store the <em>subscribers</em> expectations.</p>
<p>If you compare with existing client/server SOA solutions (in Delphi, Java,
C# or even in Go or other frameworks), this <code>interface</code>-based
callback mechanism sounds pretty unique and easy to work with.<br />
In fact, this is a good way of implementing callbacks conforming to <em>SOLID
design principles</em> on the server side, and let the <em>mORMot</em>
framework publish this mechanism in a client/server way, by using
<em>WebSockets</em>. The very same code could be used on the server side, with
no transmission nor marshaling overhead (via direct <code>interface</code>
calls), and over a network, with optimized use of resource and bandwidth (via
"fake" <code>interface</code> calls, and JSON marshalling over TCP/IP).</p>
<div style="margin-left: 2em">Ensure you <a href="http://synopse.info/files/html/Synopse%20mORMot%20Framework%20SAD%201.18.html#TITL_149">
take a look at the corresponding online documentation</a>, which would be
updated often than this blog article!<br />
The ORM part of the framework uses this <em>Publish-Subscribe</em> feature to
implement <a href="https://blog.synopse.info?post/post/2015/04/06/Real-Time-ORM-Master/Slave-Replication-via-WebSockets">Real-Time
master/slave replication</a>.<br />
Feedback is <a href="http://synopse.info/forum/viewtopic.php?pid=15632#p15632">welcome on our
forum</a>, as usual.</div>Real-Time ORM Master/Slave Replication via WebSocketsurn:md5:1709c8e98682a9bc997b21cade0a10352015-04-06T21:01:00+02:002015-04-06T20:09:38+02:00AB4327-GANDImORMot FrameworkAJAXblogDatabaseDelphiDocumentationEventCollaborationEventSourcinginterfaceModelORMreplicationSOLIDSourceSQLite3SynopseWebSockets<p>In a <a href="https://blog.synopse.info?post/post/2015/03/31/ORM-Master/Slave-Replication">previous
article</a>, we presented how Master/Slave replication may be easily
implemented in <em>mORMot</em>'s RESTful ORM.<br />
Do not forget to <a href="http://synopse.info/files/html/Synopse%20mORMot%20Framework%20SAD%201.18.html#TITL_147">
visit the corresponding paragraphs of our online documentation</a>, which has
been updated, and is more accurate!</p>
<p><img src="http://www.ibm.com/developerworks/library/wa-reverseajax2/fig01.gif" alt="" /></p>
<p>Sometimes, the on-demand synchronization is not enough.<br />
So we have just introduced real-time replication via <em>WebSockets</em>.<br />
For instance, you may need to:</p>
<ul>
<li>Synchronize a short list of always evolving items which should be reflected
as soon as possible;</li>
<li>Involve some kind of ACID-like behavior (e.g. handle money!) in your
replicated data;</li>
<li>Replicate not from a GUI application, but from a service, so use of a
<code>TTimer</code> is not an option;</li>
<li>Combine REST requests (for ORM or services) and master/slave ORM
replication on the same wire, e.g. in a multi-threaded application.</li>
</ul>
<p>In this case, the framework is able to use <em>WebSockets</em> and
asynchronous callbacks to let the master/slave replication - see <em><a href="http://synopse.info/files/html/Synopse%20mORMot%20Framework%20SAD%201.18.html#TITL_149">
Asynchronous callbacks</a></em> - take place without the need to ask explicitly
for pending data.<br />
You would need to use
<code>TSQLRestServer.RecordVersionSynchronizeMasterStart</code>,
<code>TSQLRestServer.RecordVersionSynchronizeSlaveStart</code> and
<code>TSQLRestServer.RecordVersionSynchronizeSlaveStop</code> methods over the
proper kind of bidirectional connection.</p> <p>The first requirement is to allow <em>WebSockets</em> on your
<em>Master</em> HTTP server, so initialize the <code>TSQLHttpServer</code>
class as a <code>useBidirSocket</code> kind of server - see <em><a href="http://synopse.info/files/html/Synopse%20mORMot%20Framework%20SAD%201.18.html#TITL_140">
Network and Internet access via HTTP</a></em>:</p>
<pre>
MasterServer := TSQLRestServerDB.Create(MasterModel,'master.db3');
HttpMasterServer := TSQLHttpServer.Create('8888',[MasterServer],'+',useBidirSocket);
HttpMasterServer.WebSocketsEnable(Server,'PrivateAESEncryptionKey');
</pre>
<p>On the <em>Slave</em> side, the HTTP client should also be upgraded to
support <em>WebSockets</em>:</p>
<pre>
MasterClient := TSQLHttpClientWebsockets.Create('127.0.0.1',HTTP_DEFAULTPORT,MasterModel);
MasterClient.WebSocketsUpgrade('PrivateAESEncryptionKey');
</pre>
<p>Of course, the model should match for both <code>MasterServer</code> and
<code>MasterClient</code> instances.<br />
As the <em>WebSockets</em> protocol definition - here above the same
<code>'PrivateAESEncryptionKey'</code> private key.</p>
<p>Then you enable the real-time replication service on the <em>Master</em>
side:</p>
<pre>
MasterServer.RecordVersionSynchronizeMasterStart;
</pre>
<p>In practice, it will publish a <code>IServiceRecordVersion
interface</code>-based service on the server side - see <em><a href="http://synopse.info/files/html/Synopse%20mORMot%20Framework%20SAD%201.18.html#TITL_63">
Client-Server services via interfaces</a></em>.</p>
<p>Assuming that the <em>slave</em> database has been defined as such:</p>
<pre>
SlaveServer := TSQLRestServerDB.Create(SlaveModel,'slave.db3');
</pre>
<p>(in this case, the <code>SlaveModel</code> may not be the same as the
<code>MasterModel</code>, but <code>TSQLRecordPeopleVersioned</code> should be
part of both models)<br />
Then you can initiate real-time replication from the <em>slave</em> side with a
single line, for a given table:</p>
<pre>
SlaveServer.RecordVersionSynchronizeSlaveStart(TSQLRecordPeopleVersioned,MasterClient);
</pre>
<p>The above command will subscribe to the remote <code>MasterSlave</code>
replication service (i.e. <code>IServiceRecordVersion interface</code>), to
receive any change concerning the <code>TSQLRecordPeopleVersioned</code> ORM
table, using the <code>MasterClient</code> connection via <em>WebSockets</em>,
and persist all updates into the local <code>SlaveServer</code> database.</p>
<p>To stop the real-time notification for this ORM table, you could
execute:</p>
<pre>
SlaveServer.RecordVersionSynchronizeSlaveStop(TSQLRecordPeopleVersioned);
</pre>
<p>Even if you do not call <code>RecordVersionSynchronizeSlaveStop()</code>,
the replication will be stopped when the main <code>SlaveServer</code> instance
will be released, and the <code>MasterServer</code> be <em>unsubscribe</em>
this connection for its internal notification list.</p>
<p>The real-time notification details have been tuned, to consume as minimum
bandwidth and resources as possible.<br />
For instance, if several modifications are to be notified on a slave connection
in a short amount of time, the master is able to gather those modifications as
a single <em>WebSockets</em> frame, which would be applied as a whole to the
slave database, in a <a href="http://synopse.info/files/html/Synopse%20mORMot%20Framework%20SAD%201.18.html#TITL_28">
single BATCH transaction</a>.</p>
<p>Feedback is <a href="http://synopse.info/forum/viewtopic.php?pid=15577#p15577">welcome in our
forum</a>, as usual!</p>2015: the future of mORMot is BigDataurn:md5:aad77f14b241587052c36d8614eb16372014-12-31T11:35:00+01:002014-12-31T12:17:12+01:00AB4327-GANDImORMot FrameworkbackupblogCrossPlatformDelphiLinuxmORMotORMperformancereplicationRestsecurityshardingSOASynopse<p>How would be 2015 like for our little rodents?<br />
Due to popular request of several users of <em>mORMot</em>, we identified and
designed some feature requests dedicated to <a href="http://en.wikipedia.org/wiki/Big_data">BigData</a> process.</p>
<p><img src="http://4.bp.blogspot.com/-dFLzN6qwi1I/UoELR41hJXI/AAAAAAAAAB8/9du_nNOdDtE/s1600/Big_data.ashx_.png" alt="" /></p>
<p>In fact, your <em>data</em> is the new value, especially if you propose
<em>SaaS</em> (<a href="http://en.wikipedia.org/wiki/Software_as_a_service">Software As A Service</a>)
hosting to your customers, with a farm of <em><ins>mORMot</ins></em>
servers.<br />
Recent Linux support for <em>mORMot</em> servers, together with the
high performance and installation ease of our executable, open the gate to
cheap cloud-based hosting.<br />
As a consequence, a lot of information would certainly be gathered by
your <em>mORMot</em> servers, and a single monolithic database
is not an option any more.</p>
<p>For <em>mORMot</em> solutions hosted in cloud, a lot of data may be
generated. The default <em>SQLite3</em> storage engine may be less convenient,
once it reaches some GB of file content. Backup becomes to be slow and
inefficient, and hosting this oldest data in the main DB, probably stored on an
expensive SSD, may be a lost of resource. Vertical scaling is limited by
hardware and price factors.</p>
<p>This is were <strong>data sharding</strong> comes into scene.<br />
Note that <em>sharding</em> is not replication/backup, nor clustering, nor just
spreading. We are speaking about application-level data splitting, to ease
maintenance and horizontal scalability of <em>mORMot</em> servers.</p>
<p>Data sharding could already be implemented with <em>mORMot</em> servers,
thanks to <code>TSQLRestStorage</code>:</p>
<ul>
<li>Using <code>TSQLRestStorageExternal</code>: any table may have its own
external SQL database engine, may be in its separated DB server;</li>
<li>Using <code>TSQLRestStorageMongoDB</code>: any table may be stored on a
<em>MongoDB</em> cluster, with its own sharding abilities;</li>
<li>Using <code>TSQLRestStorageRemote</code>: each table may have its own
remote ORM/REST server.</li>
</ul>
<p>But when data stored in a single table tends to grow without limit, this
feature is not enough.<br />
Let's see how the close future of <em>mORMot</em> looks like.</p> <p>The following features are on the road map:</p>
<ul>
<li><a href="http://synopse.info/fossil/info/3453f314d9">Auto-Synch</a> between
<em>mORMot</em> servers (as cache or for branch office cache), and potentially
<em>mORMot</em> clients (offline mode);</li>
<li><a href="http://synopse.info/fossil/tktview/cde2d68eb60463">Automated
sharding</a> for BigData storage;</li>
<li><a href="http://synopse.info/fossil/tktview/17958b22c028">Monitoring</a> of
your farm of <em>mORMot</em> servers;</li>
<li><a href="http://synopse.info/fossil/tktview/9357b49fe2">Workaround to use
interfaces</a> / SOA under FPC (Linux);</li>
<li><a href="http://synopse.info/fossil/tktview/8e4c17a082">Integrate the
Monkey HTTP Server</a> for Linux;</li>
<li>Implements <a href="http://synopse.info/fossil/tktview?name=d71391c2e7">WiredTiger NoSQL
engine</a>.</li>
</ul>
<p>Please refer to each feature request ticket for design patterns, and
implementation proposal.<br />
The <a href="http://synopse.info/fossil/tktview?name=aa230e5299">publish/subscribe
event-based mechanism</a> would be first to be implemented, since it would be
used by the first items.</p>
<p>Your feedback is <a href="http://synopse.info/forum/viewtopic.php?id=2259">welcome on our forum</a>, as
usual!</p>BeDelphi 2014 Slidesurn:md5:9de645c3d544f031406c7722043755a72014-11-20T21:43:00+01:002020-07-03T09:29:59+02:00AB4327-GANDImORMot FrameworkblogDelphiDocumentationmORMotSourceSynopse<p>We just finished our Be-Delphi 2014 sessions and drank our last beers, so
here we are.<img src="http://www.be-delphi.com/cms/sites/default/files/logo-ddd.png" alt="" /></p>
<p>I published some slides for this great event.</p> <p>Perhaps too much slides and material for 1 hour and a half of presentation,
but anyway....</p>
<p>Here are the slides!</p>
<p>You can download them from this <a href="http://synopse.info/files/pdf/BeDelphi2014.pdf">BeDelphi2014.pdf</a>
link.<br />
It does reflect the latest state of our framework.<br />
Fell free to share and post your feedback!</p>
<p>Thanks you all for your contributions!</p>Meet in Belgium!urn:md5:2662a57965957eee6c157ca9f4624f802014-10-08T13:05:00+02:002014-10-08T12:06:28+02:00AB4327-GANDISynopse CompanyblogDelphimORMotORMSOASynopse <p>Just a small message to let you know that I was invited, as a speaker to the
<a href="http://www.be-delphi.com/cms/">BE Delphi 2014</a> event.<br />
This year, the sessions will focus on n-Tier development, so our little
<em>mORMot</em> does make sense in the landscape!</p>
<p><img src="http://www.be-delphi.com/cms/sites/default/files/logo-ddd.png" alt="" /></p>
<p>If you are in Belgium or in Europe, we would be very pleased to meet you
there!</p>
<p>Thanks to the BE-Delphi team to let our little Open Source project be part
of this great gathering!</p>List your app on mORMot ShowCaseurn:md5:3fceeddf112368c120584839fa413a7b2014-08-20T16:03:00+02:002014-08-20T15:04:04+02:00AB4327-GANDImORMot FrameworkblogDelphiForumsmORMotSynopse<p>If you use our little <em>mORMot</em> framework in any of your project,
commercial or not, <a href="http://synopse.info/forum/viewtopic.php?id=1954">please report it to a new
forum thread dedicated for this</a>!<br />
Sounds like if more and more projects are using our little rodent...<br />
The cross-platform clients did increase its popularity, as far as I found out,
in the last days.</p>
<p><img src="https://s3.amazonaws.com/ksr/assets/000/762/944/61215d867a05eb50d3b101ba4c014c77_large.jpg?1374352449" width="350" height="340" alt="" /></p>
<p>Furthermore, if you accept that your application is quoted in our framework
documentation and web site, and/or your logo displayed, please notify it in the
application post or by email to us.</p>
<p>The first listed application - <a href="http://easymail7.com/">EasyMail7 Client-Server Email Marketing
Solution</a> - is indeed impressive, and shows well the Client-Server and
storage abilities of the framework.</p> <p><em>Warning</em>: this forum thread is only to notify about applications
created using <em>mORMot</em>.<br />
One post per application!</p>
<p>Please do not post in this thread for reactions or questions about one or
another product.<br />
If you want to discuss, please rather create a new dedicated thread
instead.</p>
<p>Thanks for your feedback!</p>Cross-Platform mORMot Clients - Smart Mobile Studiourn:md5:c91c39eba98fded8fbc28d7f62d2954b2014-08-11T20:44:00+02:002020-07-03T09:29:59+02:00AB4327-GANDImORMot FrameworkAJAXblogCrossPlatformDelphiDocumentationDTOFireMonkeyFreePascalHTMLHTTPHTTPSiOSJavaScriptJSONModelmORMotMustachenativeNextGenOpenSourceORMperformancerecordRestSOASynopseUserInterfaceweb<p>Current version of the main framework units target only <em>Win32</em> and
<em>Win64</em> systems.</p>
<p>It allows to make easy self-hosting of <em>mORMot</em> servers for local
business applications in any corporation, or pay cheap hosting in the Cloud,
since <em>mORMot</em> CPU and RAM expectations are much lower than a regular
<code>IIS-WCF-MSSQL-.Net</code> stack.<br />
But in a <em>Service-Oriented Architecture (SOA)</em>, you would probably need
to create clients for platforms outside the <em>Windows</em> world, especially
mobile devices.<br />
<img src="https://blog.synopse.info?post/public/mORMot/CrossPlatform.jpg" alt="" title="CrossPlatform, Aug 2014" /></p>
<p>A set of cross-platform client units is therefore available in the
<code>CrossPlatform</code> sub-folder of the source code repository. It allows
writing any client in modern <em>object pascal</em> language, for:</p>
<ul>
<li>Any version of <em>Delphi</em>, on any platform (<em>Mac OSX</em>, or any
mobile supported devices);</li>
<li><em>FreePascal</em> Compiler 2.7.1;</li>
<li><em>Smart Mobile Studio</em> 2.1, to create AJAX or mobile applications
(via <em>PhoneGap</em>, if needed).</li>
</ul>
<p>This series of articles will introduce you to <em>mORMot</em>'s
Cross-Platform abilities:</p>
<ul>
<li><a href="https://blog.synopse.info?post/post/2014/08/11/CrossPlatform-Clients/Units-Platforms">Units and
platforms</a>;</li>
<li><a href="https://blog.synopse.info?post/post/2014/08/11/CrossPlatform-Clients/Generate-Code">Generating
the client code wrappers</a>;</li>
<li><a href="https://blog.synopse.info?post/post/2014/08/11/CrossPlatform-Clients/Delphi-FreePascal"><em>Delphi</em> /
<em>FreePascal</em> clients</a>;</li>
<li><a href="https://blog.synopse.info?post/post/2014/08/11/CrossPlatform-Clients/SmartMobileStudio"><em>Smart Mobile
Studio</em> clients</a>.</li>
</ul>
<p>Any feedback is <a href="http://synopse.info/forum/viewtopic.php?id=1939">welcome in our forum</a>, as
usual!</p> <h3>Smart Mobile Studio client samples</h3>
<p>In addition to <em>Delphi</em> and <em>FreePascal</em> clients, our
framework is able to access any <em>mORMot</em> server from HTML5 / AJAX rich
client, thanks to <em>Smart Mobile Studio</em>.</p>
<h3>Adding two numbers in AJAX</h3>
<p>You can find in <code>SQLite3- CrossPlatform ClientsSmartMobileStudio</code>
a simple client for the <code>TServiceCalculator.Add()</code> interface-based
service.<br />
If your <code>Project14ServerHttpWrapper</code> server is running, you can just
point to the supplied <code>wwwhtml</code> file in the sub-folder.<br />
You would then see a web page with a "<code>Server Connect</code>" button, and
if you click on it, you would be able to add two numbers. This a full HTML5 web
application, connecting securely to your <em>mORMot</em> server, which will
work from any desktop browser (on <em>Windows</em>, <em>Mac OS X</em>, or
<em>Linux</em>), or from any mobile device (either <em>iPhone</em> /
<em>iPad</em> / <em>Android</em> / <em>Windows 8 Mobile</em>).</p>
<p>In order to create the application, we just clicked on "<ins>download as
file</ins>" in the <strong>SmartMobileStudio</strong> link in the web page, and
copied the generated file in the source folder of a new <em>Smart Mobile</em>
project.<br />
Of course, we did copy the needed <code>SynCrossPlatform*.pas</code> units from
the <em>mORMot</em> source code tree into the Smart library folder, as stated
above. Just ensure you run <code>CopySynCrossPlatformUnits.bat</code> from the
<code>CrossPlatform</code> folder at least once from the latest revision of the
framework source code.</p>
<p>Then, on the form visual editor, we added a <code>BtnConnect</code> button,
then a <code>PanelCompute</code> panel with two edit fields named
<code>EditA</code> and <code>EditB</code>, and two other buttons, named
<code>BtnComputeAsynch</code> and <code>BtnComputeSynch</code>. A
<code>LabelResult</code> label will be used to display the computation result.
The <code>BtnConnect</code> is a toggle which will show or display the
<code>PanelCompute</code> panel, which is hidden by default, depending on the
connection status.</p>
<p><img src="https://blog.synopse.info?post/public/mORMot/SmartCalculator.png" alt="" title="Smart Mobile Studio Calculator Service Client Sample, Aug 2014" /></p>
<p>In the <code>Form1.pas</code> unit source code side, we added a reference to
our both <code>SynCrossPlatformREST</code> and <code>mORMotClient</code> units,
and some events to the buttons:</p>
<pre>
<strong>unit</strong> Form1;
<strong>interface
uses</strong>
SmartCL.System, SmartCL.Graphics, SmartCL.Components, SmartCL.Forms,
SmartCL.Fonts, SmartCL.Borders, SmartCL.Application, SmartCL.Controls.Panel,
SmartCL.Controls.<strong>Label</strong>, SmartCL.Controls.EditBox, SmartCL.Controls.Button,
<span style="background-color:yellow;">SynCrossPlatformREST, mORMotClient;</span>
<br /><strong>type</strong>
TForm1 = <strong>class</strong>(TW3Form)
<strong>procedure</strong> BtnComputeSynchClick(Sender: TObject);
<strong>procedure</strong> BtnComputeAsynchClick(Sender: TObject);
<strong>procedure</strong> BtnConnectClick(Sender: TObject);
<strong>private</strong>
<em>{$I 'Form1:intf'}</em>
<strong>protected</strong>
<span style="background-color:yellow;">Client: TSQLRestClientURI;</span>
<strong>procedure</strong> InitializeForm; <strong>override</strong>;
<strong>procedure</strong> InitializeObject; <strong>override</strong>;
<strong>procedure</strong> Resize; <strong>override</strong>;
<strong>end</strong>;
</pre>
<p>The <code>BtnConnect</code> event will connect asynchronously to the server,
using <code>'User'</code> as log-on name, and <code>'synopse'</code> as
password (those as the framework defaults).<br />
We just use the <code>GetClient()</code> function, as published in our
generated <code>mORMotClient.pas</code> unit:</p>
<pre>
<em>/// create a TSQLRestClientHTTP instance and connect to the server
// - it will use by default port 888
// - secure connection will be established via TSQLRestServerAuthenticationDefault
// with the supplied credentials
// - request will be asynchronous, and trigger onSuccess or onError event</em>
<strong>procedure</strong> GetClient(<strong>const</strong> aServerAddress, aUserName,aPassword: <strong>string</strong>;
onSuccess, onError: TSQLRestEvent; aServerPort: integer=SERVER_PORT);
</pre>
<p>It uses two callbacks, the first in case of success, and the second
triggered on failure. On success, we will set the global <code>Client</code>
variable with the <code>TSQLRestClientURI</code> instance just created, then
display the two fields and compute buttons:</p>
<pre>
<strong>procedure</strong> TForm1.BtnConnectClick(Sender: TObject);
<strong>begin</strong>
<strong>if</strong> Client=<strong>nil then</strong>
<span style="background-color:yellow;">GetClient('127.0.0.1','User','synopse',</span>
<span style="background-color:yellow;">lambda (aClient: TSQLRestClientURI)</span>
PanelCompute.Visible := true;
W3Label1.Visible := true;
W3Label2.Visible := true;
LabelConnect.Caption := '';
BtnConnect.Caption := 'Disconnect';
LabelResult.Caption := '';
<span style="background-color:yellow;">Client := aClient;</span>
<strong>end</strong>,
lambda
ShowMessage('Impossible to connect to the server!');
<strong>end</strong>)
<strong>else begin</strong>
PanelCompute.Visible := false;
BtnConnect.Caption := 'Server Connect';
Client.Free;
Client := <strong>nil</strong>;
<strong>end</strong>;
<strong>end</strong>;
</pre>
<p>The <code>GetClient()</code> function expects two callbacks, respectively
<code>onSuccess</code> and <code>onError</code>, which are implemented here
with two <em>SmartPascal</em> <code>lambda</code> blocks.</p>
<p>Now that we are connected to the server, let's do some useful
computation!<br />
As you can see in the <code>mORMotClient.pas</code> generated unit, our
interface-based service can be accessed via a <em>SmartPascal</em>
<code>TServiceCalculator</code> class (and not an <code>interface</code>), with
two variations of each methods: one <em>asynchronous</em> method - e.g.
<code>TServiceCalculator.Add()</code> - expecting success/error callbacks, and
one <em>synchronous</em> (blocking) method - e.g.
<code>TServiceCalculator._Add()</code>:</p>
<pre>
<strong>type</strong>
<em>/// service accessible via http://localhost:888/root/Calculator</em>
<em>// - this service will run in sicShared mode</em>
<em>// - synchronous and asynchronous methods are available, depending on use case</em>
<em>// - synchronous _*() methods will block the browser execution, so won't be</em>
<em>// appropriate for long process - on error, they may raise EServiceException</em>
TServiceCalculator = <strong>class</strong>(TServiceClientAbstract)
<strong>public</strong>
<em>/// will initialize an access to the remote service</em>
<strong>constructor</strong> Create(aClient: TSQLRestClientURI); <strong>override</strong>;
<strong>procedure</strong> Add(n1: integer; n2: integer;
onSuccess: <strong>procedure</strong>(Result: integer); onError: TSQLRestEvent);
<strong>function</strong> _Add(<strong>const</strong> n1: integer; <strong>const</strong> n2: integer): integer;
<strong>end</strong>;
</pre>
<p>We can therefore execute asynchronously the <code>Add()</code> service as
such:</p>
<pre>
<strong>procedure</strong> TForm1.BtnComputeAsynchClick(Sender: TObject);
<strong>begin</strong>
TServiceCalculator.Create(Client).Add(
StrToInt(EditA.Text),StrToInt(EditB.Text),
lambda (res: integer)
LabelResult.Caption := format('Result = %d',[res]);
<strong>end</strong>,
lambda
ShowMessage('Error calling the method!');
<strong>end</strong>);
<strong>end</strong>;
</pre>
<p>Or execute synchronously the <code>_Add()</code> service:</p>
<pre>
<strong>procedure</strong> TForm1.BtnComputeSynchClick(Sender: TObject);
<strong>begin</strong>
LabelResult.Caption := format('Result = %d',
[TServiceCalculator.Create(Client)._Add(
StrToInt(EditA.Text),StrToInt(EditB.Text))]);
<strong>end</strong>;
</pre>
<p>Of course, the synchronous code is much easier to follow and maintain. To be
fair, the <em>SmartPascal</em> <code>lambda</code> syntax is not difficult to
read nor write. In the browser debugger, you can easily set a break point
within any <code>lambda</code> block, and debug your code.</p>
<p>Note that if the server is slow to answer, your whole web application will
be unresponsive, and the browser may even complain about the page, proposing
the kill its process!<br />
As a consequence, simple services may be written in a synchronous manner, but
your serious business code should rather use asynchronous callbacks, just as
with any modern AJAX application.</p>
<p>Thanks to the <em>Smart Linking</em> feature of its compiler, only the used
version of the unit will be converted to <em>JavaScript</em> and included in
the final <code>index.html</code> HTML5 file. So having both synchronous and
asynchronous versions of each method at hand is not an issue.</p>
<h3>CRUD/ORM remote access</h3>
<p>If the server did have some ORM model, its <code>TSQLRecord</code> classes
will also be part of the <code>mORMotClient.pas</code> generated unit. All
types, even complex record structures, will be marshaled as expected.</p>
<p>For instance, if you run the <code>RegressionTestsServer.dpr</code> server
(available in the same folder), a much more complete unit could be generated
from <code>http://localhost:888/root/wrapper</code>:</p>
<pre>
<strong>type</strong> <em>// define some enumeration types, used below</em>
TPeopleSexe = (sFemale, sMale);
TRecordEnum = (reOne, reTwo, reLast);
<br /><strong>type</strong> <em>// define some record types, used as properties below</em>
TTestCustomJSONArraySimpleArray = <strong>record</strong>
F: <strong>string</strong>;
G: <strong>array of string</strong>;
H: <strong>record</strong>
H1: integer;
H2: <strong>string</strong>;
H3: <strong>record</strong>
H3a: boolean;
H3b: TSQLRawBlob;
<strong>end</strong>;
<strong>end</strong>;
I: TDateTime;
J: <strong>array of record</strong>
J1: byte;
J2: TGUID;
J3: TRecordEnum;
<strong>end</strong>;
<strong>end</strong>;
<strong>type</strong>
<em>/// service accessible via http://localhost:888/root/Calculator</em>
<em>// - this service will run in sicShared mode</em>
<em>// - synchronous and asynchronous methods are available, depending on use case</em>
<em>// - synchronous _*() methods will block the browser execution, so won't be</em>
<em>// appropriate for long process - on error, they may raise EServiceException</em>
TServiceCalculator = <strong>class</strong>(TServiceClientAbstract)
<strong>public</strong>
<em>/// will initialize an access to the remote service</em>
<strong>constructor</strong> Create(aClient: TSQLRestClientURI); <strong>override</strong>;
<strong>procedure</strong> Add(n1: integer; n2: integer;
onSuccess: <strong>procedure</strong>(Result: integer); onError: TSQLRestEvent);
<strong>function</strong> _Add(<strong>const</strong> n1: integer; <strong>const</strong> n2: integer): integer;
<strong>procedure</strong> ToText(Value: currency; Curr: <strong>string</strong>; Sexe: TPeopleSexe; Name: <strong>string</strong>;
onSuccess: <strong>procedure</strong>(Sexe: TPeopleSexe; Name: <strong>string</strong>); onError: TSQLRestEvent);
<strong>procedure</strong> _ToText(<strong>const</strong> Value: currency; <strong>const</strong> Curr: RawUTF8; <strong>var</strong> Sexe: TPeopleSexe; <strong>var</strong> Name: RawUTF8);
<strong>procedure</strong> RecordToText(Rec: TTestCustomJSONArraySimpleArray;
onSuccess: <strong>procedure</strong>(Rec: TTestCustomJSONArraySimpleArray; Result: <strong>string</strong>); onError: TSQLRestEvent);
<strong>function</strong> _RecordToText(<strong>var</strong> Rec: TTestCustomJSONArraySimpleArray): <strong>string</strong>;
<strong>end</strong>;
<br /> <em>/// map "People" table</em>
TSQLRecordPeople = <strong>class</strong>(TSQLRecord)
<strong>protected</strong>
fFirstName: <strong>string</strong>;
fLastName: <strong>string</strong>;
fData: TSQLRawBlob;
fYearOfBirth: integer;
fYearOfDeath: word;
fSexe: TPeopleSexe;
fSimple: TTestCustomJSONArraySimpleArray;
<em>// those overriden methods will emulate the needed RTTI</em>
<strong>class function</strong> ComputeRTTI: TRTTIPropInfos; <strong>override</strong>;
<strong>procedure</strong> SetProperty(FieldIndex: integer; <strong>const</strong> Value: <strong>variant</strong>); <strong>override</strong>;
<strong>function</strong> GetProperty(FieldIndex: integer): <strong>variant</strong>; <strong>override</strong>;
<strong>public</strong>
<strong>property</strong> FirstName: <strong>string read</strong> fFirstName <strong>write</strong> fFirstName;
<strong>property</strong> LastName: <strong>string read</strong> fLastName <strong>write</strong> fLastName;
<strong>property</strong> Data: TSQLRawBlob <strong>read</strong> fData <strong>write</strong> fData;
<strong>property</strong> YearOfBirth: integer <strong>read</strong> fYearOfBirth <strong>write</strong> fYearOfBirth;
<strong>property</strong> YearOfDeath: word <strong>read</strong> fYearOfDeath <strong>write</strong> fYearOfDeath;
<strong>property</strong> Sexe: TPeopleSexe <strong>read</strong> fSexe <strong>write</strong> fSexe;
<strong>property</strong> Simple: TTestCustomJSONArraySimpleArray <strong>read</strong> fSimple <strong>write</strong> fSimple;
<strong>end</strong>;
</pre>
<p>In the above code, you can see several methods to the
<code>ICalculator</code> service, some involving the complex
<code>TTestCustomJSONArraySimpleArray</code> record type. The
<code>implementation</code> section of the unit will in fact allow
serialization of such records to/from JSON, even with obfuscated
<em>JavaScript</em> field names, via <code>ComputeRTTI() GetProperty()</code>
and <code>SetProperty()</code>.</p>
<p>Some <em>enumerations</em> types are also defined, so will help your
business code be very expressive, thanks to the <em>SmartPascal</em> strong
typing. This is a huge improvement when compared to <em>JavaScript</em> native
weak and dynamic typing.</p>
<p>There is a <code>TSQLRecordPeople</code> class generated, which will map the
following <em>Delphi</em> class type, as defined in the
<code>PeopleServer.pas</code> unit:</p>
<pre>
TSQLRecordPeople = <strong>class</strong>(TSQLRecord)
<strong>protected</strong>
fData: TSQLRawBlob;
fFirstName: RawUTF8;
fLastName: RawUTF8;
fYearOfBirth: integer;
fYearOfDeath: word;
fSexe: TPeopleSexe;
fSimple: TTestCustomJSONArraySimpleArray;
<strong>public</strong>
<strong>class procedure</strong> InternalRegisterCustomProperties(Props: TSQLRecordProperties); <strong>override</strong>;
<strong>published</strong>
<strong>property</strong> FirstName: RawUTF8 <strong>read</strong> fFirstName <strong>write</strong> fFirstName;
<strong>property</strong> LastName: RawUTF8 <strong>read</strong> fLastName <strong>write</strong> fLastName;
<strong>property</strong> Data: TSQLRawBlob <strong>read</strong> fData <strong>write</strong> fData;
<strong>property</strong> YearOfBirth: integer <strong>read</strong> fYearOfBirth <strong>write</strong> fYearOfBirth;
<strong>property</strong> YearOfDeath: word <strong>read</strong> fYearOfDeath <strong>write</strong> fYearOfDeath;
<strong>property</strong> Sexe: TPeopleSexe <strong>read</strong> fSexe <strong>write</strong> fSexe;
<strong>public</strong>
<strong>property</strong> Simple: TTestCustomJSONArraySimpleArray <strong>read</strong> fSimple;
<strong>end</strong>;
</pre>
<p>Here, a complex <code>TTestCustomJSONArraySimpleArray</code> record field
has been published, thanks to a manual
<code>InternalRegisterCustomProperties()</code> registration, as we already
stated above.<br />
You can see that types like <code>RawUTF8</code> were mapped to the standard
<em>SmartPascal</em> <code>string</code> type, as expected, when converted to
the <code>mORMotClient.pas</code> generated unit.</p>
<p>Your AJAX client can then access to this <code>TSQLRecordPeople</code>
content easily, via standard CRUD operations.<br />
See the <code>SQLite3- SmartMobileStudio Client</code> sample, for instance the
following line:</p>
<pre>
<span style="background-color:yellow;">people := new TSQLRecordPeople;</span>
<strong>for</strong> i := 1 <strong>to</strong> 200 <strong>do begin</strong>
<span style="background-color:yellow;">assert(client.Retrieve(i,people));</span>
assert(people.ID=i);
assert(people.FirstName='First'+IntToStr(i));
assert(people.LastName='Last'+IntToStr(i));
assert(people.YearOfBirth=id+1800);
assert(people.YearOfDeath=id+1825);
<strong>end</strong>;
</pre>
<p>Here, the <code>client</code> variable is a <code>TSQLRestClientURI</code>
instance, as returned by the <code>GetClient() onSuccess</code> callback
generated in <code>mORMotClient.pas</code>.<br />
You have <code>Add() Delete() Update() FillPrepare()
CreateAndFillPrepare()</code> and <code>Batch*()</code> methods available,
ready to safely access your data from your AJAX client.</p>
<p>If you update your data model on the server, just re-generate your
<code>mORMotClient.pas</code> unit from
<code>http://localhost:888/root/wrapper</code>, then rebuild your <em>Smart
Mobile Studio</em> project to reflect all changes made to your ORM data model,
or your SOA available services.</p>
<p>Thanks to the <em>SmartPascal</em> strong typing, any breaking change of the
server expectations would immediately be reported at compilation, and not at
runtime, as it would with regular <em>JavaScript</em> clients.</p>Cross-Platform mORMot Clients - Delphi / FreePascalurn:md5:1a7657aad2fbf5869145db279b552ec32014-08-11T20:42:00+02:002020-07-03T09:29:59+02:00AB4327-GANDImORMot FrameworkAJAXblogCrossPlatformDelphiDocumentationDTOFireMonkeyFreePascalHTMLHTTPHTTPSiOSJavaScriptJSONModelmORMotMustachenativeNextGenOpenSourceORMperformancerecordRestSOASynopseUserInterfaceweb<p>Current version of the main framework units target only <em>Win32</em> and
<em>Win64</em> systems.</p>
<p>It allows to make easy self-hosting of <em>mORMot</em> servers for local
business applications in any corporation, or pay cheap hosting in the Cloud,
since <em>mORMot</em> CPU and RAM expectations are much lower than a regular
<code>IIS-WCF-MSSQL-.Net</code> stack.<br />
But in a <em>Service-Oriented Architecture (SOA)</em>, you would probably need
to create clients for platforms outside the <em>Windows</em> world, especially
mobile devices.<br />
<img src="https://blog.synopse.info?post/public/mORMot/CrossPlatform.jpg" alt="" title="CrossPlatform, Aug 2014" /></p>
<p>A set of cross-platform client units is therefore available in the
<code>CrossPlatform</code> sub-folder of the source code repository. It allows
writing any client in modern <em>object pascal</em> language, for:</p>
<ul>
<li>Any version of <em>Delphi</em>, on any platform (<em>Mac OSX</em>, or any
mobile supported devices);</li>
<li><em>FreePascal</em> Compiler 2.7.1;</li>
<li><em>Smart Mobile Studio</em> 2.1, to create AJAX or mobile applications
(via <em>PhoneGap</em>, if needed).</li>
</ul>
<p>This series of articles will introduce you to <em>mORMot</em>'s
Cross-Platform abilities:</p>
<ul>
<li><a href="https://blog.synopse.info?post/post/2014/08/11/CrossPlatform-Clients/Units-Platforms">Units and
platforms</a>;</li>
<li><a href="https://blog.synopse.info?post/post/2014/08/11/CrossPlatform-Clients/Generate-Code">Generating
the client code wrappers</a>;</li>
<li><a href="https://blog.synopse.info?post/post/2014/08/11/CrossPlatform-Clients/Delphi-FreePascal"><em>Delphi</em> /
<em>FreePascal</em> clients</a>;</li>
<li><a href="https://blog.synopse.info?post/post/2014/08/11/CrossPlatform-Clients/SmartMobileStudio"><em>Smart Mobile
Studio</em> clients</a>.</li>
</ul>
<p>Any feedback is <a href="http://synopse.info/forum/viewtopic.php?id=1939">welcome in our forum</a>, as
usual!</p> <h3>Delphi / FreePascal client samples</h3>
<p>The "<code>27 - CrossPlatform ClientsRegressionTests.dpr</code>" sample
creates a <em>mORMot</em> server with its own ORM data model, containing a
<code>TSQLRecordPeople</code> class, and a set of interface-based SOA services,
some including complex types like a record.</p>
<p>Then this sample uses a generated <code>mORMotClient.pas</code>, retrieved
from the "<ins>download as file</ins>" link of the
<strong>CrossPlatform</strong> template above.<br />
Its set of regression tests (written using a small cross-platform
<code>TSynTest</code> unit test class) will then perform remote ORM and SOA
calls to the <code>PeopleServer</code> embedded instance, over all supported
authentication schemes - see <em><a href="https://blog.synopse.info?post/post/2011/05/24/How-to-implement-RESTful-authentication">Authentication</a></em>:</p>
<pre>
Cross Platform Units for mORMot
---------------------------------
<br />1. Running "Iso8601DateTime"
30003 tests passed in 00:00:018
2. Running "Base64Encoding"
304 tests passed in 00:00:000
3. Running "JSON"
18628 tests passed in 00:00:056
4. Running "Model"
1013 tests passed in 00:00:003
5. Running "Cryptography"
4 tests passed in 00:00:000
<br />Tests failed: 0 / 49952
Time elapsed: 00:00:080
<br />Cross Platform Client for mORMot without authentication
---------------------------------------------------------
<br />1. Running "Connection"
2 tests passed in 00:00:010
2. Running "ORM"
4549 tests passed in 00:00:160
3. Running "ORMBatch"
4564 tests passed in 00:00:097
4. Running "Services"
26253 tests passed in 00:00:302
5. Running "CleanUp"
1 tests passed in 00:00:000
<br />Tests failed: 0 / 35369
Time elapsed: 00:00:574
<br />Cross Platform Client for mORMot using TSQLRestServerAuthenticationNone
-------------------------------------------------------------------------
...
<br />Cross Platform Client for mORMot using TSQLRestServerAuthenticationDefault
----------------------------------------------------------------------------
...
</pre>
<p>The generated <code>mORMotClient.pas</code> unit is used for all
"<code>Cross Platform Client</code>" tests above, covering both ORM and SOA
features of the framework.</p>
<h3>Connection to the server</h3>
<p>You could manually connect to a <em>mORMot</em> server as such:</p>
<pre>
<strong>var</strong> Model: TSQLModel;
Client: TSQLRestClientHTTP;
...
Model := TSQLModel.Create([TSQLAuthUser,TSQLAuthGroup,TSQLRecordPeople]);
Client := TSQLRestClientHTTP.Create('localhost',SERVER_PORT,Model);
<strong>if not</strong> Client.Connect <strong>then</strong>
<strong>raise</strong> Exception.Create('Impossible to connect to the server');
<strong>if</strong> Client.ServerTimeStamp=0 <strong>then</strong>
<strong>raise</strong> Exception.Create('Incorrect server');
<strong>if not</strong> Client.SetUser(TSQLRestAuthenticationDefault,'User','synopse') <strong>then</strong>
<strong>raise</strong> Exception.Create('Impossible to authenticate to the server');
...
</pre>
<p>Or you may use the <code>GetClient()</code> function generated in
<code>mORMotClient.pas</code>:</p>
<pre>
<em>/// create a TSQLRestClientHTTP instance and connect to the server
// - it will use by default port 888
// - secure connection will be established via TSQLRestServerAuthenticationDefault
// with the supplied credentials - on connection or authentication error,
// this function will raise a corresponding exception</em>
<strong>function</strong> GetClient(<strong>const</strong> aServerAddress, aUserName,aPassword: <strong>string</strong>;
aServerPort: integer=SERVER_PORT): TSQLRestClientHTTP;
</pre>
<p>Which could be used as such:</p>
<pre>
<strong>var</strong> Client: TSQLRestClientHTTP;
...
Client := GetClient('localhost','User','synopse')
</pre>
<p>The data model and the expected authentication scheme were included in the
<code>GetClient()</code> function, which will raise the expected
<code>ERestException</code> in case of any connection or authentication
issue.</p>
<h3>CRUD/ORM remote access</h3>
<p>Thanks to <code>SynCrossPlatform*</code> units, you could easily perform any
remote ORM operation on your <em>mORMot</em> server, with the usual
<code>TSQLRest</code> CRUD methods.<br />
For instance, the <code>RegressionTests.dpr</code> sample performs the
following operations</p>
<pre>
<span style="background-color:yellow;">fClient.CallBackGet('DropTable',[],Call,TSQLRecordPeople); <em>// call of method-based service</em></span>
check(Call.OutStatus=HTML_SUCCESS);
people := TSQLRecordPeople.Create; <em>// create a record ORM</em>
<strong>try</strong>
<strong>for</strong> i := 1 <strong>to</strong> 200 <strong>do begin</strong>
people.FirstName := 'First'+IntToStr(i);
people.LastName := 'Last'+IntToStr(i);
people.YearOfBirth := i+1800;
people.YearOfDeath := i+1825;
people.Sexe := TPeopleSexe(i <strong>and</strong> 1);
<span style="background-color:yellow;">check(Client.Add(people,true)=i); <em>// add one record</em></span>
<strong>end</strong>;
<strong>finally</strong>
people.Free;
<strong>end</strong>;
...
<span style="background-color:yellow;">people := TSQLRecordPeople.CreateAndFillPrepare(fClient,'',</span>
<span style="background-color:yellow;">'yearofbirth=?',[1900]); <em>// parameterized query returning one or several rows</em></span>
<strong>try</strong>
n := 0;
<span style="background-color:yellow;"><strong>while</strong> people.FillOne <strong>do begin</strong></span>
inc(n);
check(people.ID=100);
check(people.FirstName='First100');
check(people.LastName='Last100');
check(people.YearOfBirth=1900);
check(people.YearOfDeath=1925);
<strong>end</strong>;
check(n=1); <em>// we expected only one record here</em>
<strong>finally</strong>
people.Free;
<strong>end</strong>;
<strong>for</strong> i := 1 <strong>to</strong> 200 <strong>do</strong>
<strong>if</strong> i <strong>and</strong> 15=0 <strong>then</strong>
<span style="background-color:yellow;">fClient.Delete(TSQLRecordPeople,i) <strong>else</strong> <em>// record deletion</em></span>
<strong>if</strong> i <strong>mod</strong> 82=0 <strong>then begin</strong>
people := TSQLRecordPeople.Create;
<strong>try</strong>
id := i+1;
people.ID := i;
people.YearOfBirth := id+1800;
people.YearOfDeath := id+1825;
<span style="background-color:yellow;">check(fClient.Update(people,'YEarOFBIRTH,YEarOfDeath')); <em>// record modification</em></span>
<strong>finally</strong>
people.Free;
<strong>end</strong>;
<strong>end</strong>;
<strong>for</strong> i := 1 <strong>to</strong> 200 <strong>do begin</strong>
<span style="background-color:yellow;">people := TSQLRecordPeople.Create(fClient,i); <em>// retrieve one instance from ID</em></span>
<strong>try</strong>
<strong>if</strong> i <strong>and</strong> 15=0 <strong>then</strong> <em>// was deleted</em>
Check(people.ID=0) <strong>else begin</strong>
<strong>if</strong> i <strong>mod</strong> 82=0 <strong>then</strong>
id := i+1 <strong>else</strong> <em>// was modified</em>
id := i;
Check(people.ID=i);
Check(people.FirstName='First'+IntToStr(i));
Check(people.LastName='Last'+IntToStr(i));
Check(people.YearOfBirth=id+1800);
Check(people.YearOfDeath=id+1825);
Check(ord(people.Sexe)=i <strong>and</strong> 1);
<strong>end</strong>;
<strong>finally</strong>
people.Free;
<strong>end</strong>;
<strong>end</strong>;
</pre>
<p>As we already stated, BATCH mode is also supported, with the classic
<em>mORMot</em> syntax:</p>
<pre>
...
res: TIntegerDynArray;
...
<span style="background-color:yellow;">fClient.BatchStart(TSQLRecordPeople);</span>
people := TSQLRecordPeople.Create;
<strong>try</strong>
<strong>for</strong> i := 1 <strong>to</strong> 200 <strong>do begin</strong>
people.FirstName := 'First'+IntToStr(i);
people.LastName := 'Last'+IntToStr(i);
people.YearOfBirth := i+1800;
people.YearOfDeath := i+1825;
<span style="background-color:yellow;">fClient.BatchAdd(people,true);</span>
<strong>end</strong>;
<strong>finally</strong>
people.Free;
<strong>end</strong>;
<span style="background-color:yellow;">fClient.fBatchSend(res)=HTML_SUCCESS);</span>
check(length(res)=200);
<strong>for</strong> i := 1 <strong>to</strong> 200 <strong>do</strong>
check(res[i-1]=i); <em>// server returned the IDs of the newly created records</em>
</pre>
<p>Those <code>BatchAdd</code> / <code>BatchDelete</code> /
<code>BatchUpdate</code> methods of <code>TSQLRest</code> have the benefit to
introduce at client level:</p>
<ul>
<li>Much higher performance, especially on multi-insertion or multi-update of
data;</li>
<li>Transactional support: <code>TSQLRest.BatchStart()</code> has an optional
<code>AutomaticTransactionPerRow</code> parameter, set to <code>10000</code> by
default, which will create a server-side transaction during the write process,
and an ACID rollback in case of any failure.</li>
</ul>
<p>You can note that all above code has exactly the same structure and methods
than standard <em>mORMot</em> clients.</p>
<p>The generated <code>mORMotClient.pas</code> unit contains all needed
<code>TSQLRecord</code> types, and its used properties, including enumerations
or complex records. The only dependency of this unit are
<code>SynCrossPlatform*</code> units, so would be perfectly cross-platform
(whereas our main <code>SynCommons.pas</code> and <code>mORMot.pas</code> units
do target only <em>Win32</em> and <em>Win64</em>).</p>
<p>As a result, you are able to <em>share</em> server and client code between a
Windows project and any supported platform, even AJAX (see "<em>Smart Mobile
Studio client samples</em>" below). A shared unique code base would eventually
reduce both implementation and debugging time, which is essential to unleash
your business code potential and maximize your ROI.</p>
<h3>Service consumption</h3>
<p>The ultimate goal of the <em>mORMot</em> framework is to publish your
business via a <em>Service-Oriented Architecture (SOA)</em>.<br />
As a consequence, those services should be made available from any kind of
device or platform, even outside the <em>Windows</em> world. The server is able
to generate client wrappers code, which could be used to consume any
<em>Client-Server services via interfaces</em> using any supported
authentication scheme - see <em><a href="https://blog.synopse.info?post/post/2013/06/07/Authentication-and-Authorization">Authentication</a></em>.</p>
<p>Here is an extract of the <code>mORMotClient.pas</code> unit as generated
for the <code>RegressionTests.dpr</code> sample:</p>
<pre>
<strong>type</strong>
<em>/// service implemented by TServiceCalculator</em>
<em>// - you can access this service as such:</em>
<em>// !var aCalculator: ICalculator;</em>
<em>// !begin</em>
<em>// ! aCalculator := TCalculator.Create(aClient);</em>
<em>// ! // now you can use aCalculator methods</em>
<em>// !...</em>
ICalculator = <strong>interface</strong>(IServiceAbstract)
['{9A60C8ED-CEB2-4E09-87D4-4A16F496E5FE}']
<strong>function</strong> Add(<strong>const</strong> n1: integer; <strong>const</strong> n2: integer): integer;
<strong>procedure</strong> ToText(<strong>const</strong> Value: currency; <strong>const</strong> Curr: <strong>string</strong>; <strong>var</strong> Sexe: TPeopleSexe; <strong>var</strong> Name: <strong>string</strong>);
<strong>function</strong> RecordToText(<strong>var</strong> Rec: TTestCustomJSONArraySimpleArray): <strong>string</strong>;
<strong>end</strong>;
<br /> <em>/// implements ICalculator from http://localhost:888/root/Calculator</em>
<em>// - this service will run in sicShared mode</em>
TServiceCalculator = <strong>class</strong>(TServiceClientAbstract,ICalculator)
<strong>public</strong>
<strong>constructor</strong> Create(aClient: TSQLRestClientURI); <strong>override</strong>;
<strong>function</strong> Add(<strong>const</strong> n1: integer; <strong>const</strong> n2: integer): integer;
<strong>procedure</strong> ToText(<strong>const</strong> Value: currency; <strong>const</strong> Curr: <strong>string</strong>; <strong>var</strong> Sexe: TPeopleSexe; <strong>var</strong> Name: <strong>string</strong>);
<strong>function</strong> RecordToText(<strong>var</strong> Rec: TTestCustomJSONArraySimpleArray): <strong>string</strong>;
<strong>end</strong>;
</pre>
<p>As you can see, a dedicated class has been generated to consume the
server-side <code>ICalculator</code> interface-based service, in its own
<code>ICalculator</code> client-side type.<br />
It is able to handle complex types, like enumerations (e.g.
<code>TPeopleSexe</code>) and records (e.g.
<code>TTestCustomJSONArraySimpleArray</code>), which are also defined in the
very same <code>mORMotClient.pas</code> unit.<br />
You can note that the <code>RawUTF8</code> type has been changed into the
standard <em>Delphi</em> / <em>FreePascal</em> <code>string</code> type, since
it is the native type used by our <code>SynCrossPlatformJSON.pas</code> unit
for all its JSON marshaling. Of course, under latest version of <em>Delphi</em>
and <em>FreePascal</em>, this kind of content may be Unicode encoded (either as
UTF-16 for the <code>string</code> = <code>UnicodeString</code> <em>Delphi</em>
type, or as UTF-8 for the <em>FreePascal</em> / <em>Lazarus</em>
<code>string</code> type).</p>
<p>The supplied regression tests show how to use remotely those services:</p>
<pre>
<span style="background-color:yellow;"><strong>var</strong> calc: ICalculator;</span>
i,j: integer;
sex: TPeopleSexe;
name: <strong>string</strong>;
...
<span style="background-color:yellow;">calc := TServiceCalculator.Create(fClient);</span>
check(calc.InstanceImplementation=sicShared);
check(calc.ServiceName='Calculator');
<strong>for</strong> i := 1 <strong>to</strong> 200 <strong>do</strong>
<span style="background-color:yellow;">check(calc.Add(i,i+1)=i*2+1);</span>
<strong>for</strong> i := 1 <strong>to</strong> 200 <strong>do begin</strong>
sex := TPeopleSexe(i <strong>and</strong> 1);
name := 'Smith';
<span style="background-color:yellow;">calc.ToText(i,'$',sex,name);</span>
check(sex=sFemale);
check(name=format('$ %d for %s Smith',[i,SEX_TEXT[i <strong>and</strong> 1]]));
<strong>end</strong>;
...
</pre>
<p>As with regular <em>mORMot</em> client code, a
<code>TServiceCalculator</code> instance is created and is assigned to a
<code>ICalculator</code> local variable. As such, no <code>try ... finally
Calc.Free end</code> block is mandatory here, to avoid any memory leak: the
compiler will create such an hidden block for the <code>Calc:
ICalculator</code> variable scope.</p>
<p>The service-side contract of the <code>ICalculator</code> signature is
retrieved and checked within <code>TServiceCalculator.Create</code>, and would
raise an <code>ERestException</code> if it does not match the contract
identified in <code>mORMotClient.pas</code>.</p>
<p>The cross-platform clients are able to manage the service instance
life-time, especially the <code>sicPerClient</code> mode. In this case, an
implementation class instance will be created on the server for each client,
until the corresponding <code>interface</code> instance will released (i.e. out
of scope or assigned to <code>nil</code>), which will release the server-side
instance - just like with a regular <em>mORMot</em> client code.</p>
<p>Note that all process here is executed <em>synchronously</em>, i.e. in
blocking mode. It is up to you to ensure that your application is able to still
be responsive, even if the server does a lot of process, so may be late to
answer. A dedicated thread may help in this case.</p>Cross-Platform mORMot Clients - Generating Code Wrappersurn:md5:86cc040b427c36a5c9fec81b3bc9a9b32014-08-11T20:41:00+02:002014-08-12T08:54:36+02:00AB4327-GANDImORMot FrameworkAJAXblogCrossPlatformDelphiDocumentationDTOFireMonkeyFreePascalHTMLHTTPHTTPSiOSJavaScriptJSONModelmORMotMustachenativeNextGenOpenSourceORMperformancerecordRestSOASynopseUserInterfaceweb<p>Current version of the main framework units target only <em>Win32</em> and
<em>Win64</em> systems.</p>
<p>It allows to make easy self-hosting of <em>mORMot</em> servers for local
business applications in any corporation, or pay cheap hosting in the Cloud,
since <em>mORMot</em> CPU and RAM expectations are much lower than a regular
<code>IIS-WCF-MSSQL-.Net</code> stack.<br />
But in a <em>Service-Oriented Architecture (SOA)</em>, you would probably need
to create clients for platforms outside the <em>Windows</em> world, especially
mobile devices.<br />
<img src="https://blog.synopse.info?post/public/mORMot/CrossPlatform.jpg" alt="" title="CrossPlatform, Aug 2014" /></p>
<p>A set of cross-platform client units is therefore available in the
<code>CrossPlatform</code> sub-folder of the source code repository. It allows
writing any client in modern <em>object pascal</em> language, for:</p>
<ul>
<li>Any version of <em>Delphi</em>, on any platform (<em>Mac OSX</em>, or any
mobile supported devices);</li>
<li><em>FreePascal</em> Compiler 2.7.1;</li>
<li><em>Smart Mobile Studio</em> 2.1, to create AJAX or mobile applications
(via <em>PhoneGap</em>, if needed).</li>
</ul>
<p>This series of articles will introduce you to <em>mORMot</em>'s
Cross-Platform abilities:</p>
<ul>
<li><a href="https://blog.synopse.info?post/post/2014/08/11/CrossPlatform-Clients/Units-Platforms">Units and
platforms</a>;</li>
<li><a href="https://blog.synopse.info?post/post/2014/08/11/CrossPlatform-Clients/Generate-Code">Generating
the client code wrappers</a>;</li>
<li><a href="https://blog.synopse.info?post/post/2014/08/11/CrossPlatform-Clients/Delphi-FreePascal"><em>Delphi</em> /
<em>FreePascal</em> clients</a>;</li>
<li><a href="https://blog.synopse.info?post/post/2014/08/11/CrossPlatform-Clients/SmartMobileStudio"><em>Smart Mobile
Studio</em> clients</a>.</li>
</ul>
<p>Any feedback is <a href="http://synopse.info/forum/viewtopic.php?id=1939">welcome in our forum</a>, as
usual!</p> <h3>Generating client wrappers</h3>
<p>Even if it feasible to write the client code by hand, your <em>mORMot</em>
server is able to create the source code needed for client access, via a
dedicated method-based service, and set of <em>Mustache</em>-based templates -
see <em><a href="https://blog.synopse.info?post/post/2014/04/28/Mustache-Logic-less-templates-for-Delphi-part-1">Mustache
template engine</a></em>.</p>
<p>The following templates are available in the
<code>CrossPlatformtemplates</code> folder:</p>
<table>
<tbody>
<tr>
<td><strong>Unit Name</strong></td>
<td><strong>Compiler Target</strong></td>
</tr>
<tr>
<td><code>CrossPlatform.pas.mustache</code></td>
<td><em>Delphi</em> / FPC SynCrossPlatform* units</td>
</tr>
<tr>
<td><code>Delphi.pas.mustache</code></td>
<td><em>Delphi</em> Win32/Win64 <em>mORMot</em> units</td>
</tr>
<tr>
<td><code>SmartMobileStudio.pas.mustache</code></td>
<td>Smart Mobile Studio 2.1</td>
</tr>
</tbody>
</table>
<p>In the future, other wrappers may be added. And you can write your own,
which could be included within the framework source! Your input is warmly
welcome, especially if you want to write a template for <em>Java</em> or
<code>C#</code> client. The generated data context already contains the data
types corresponding to those compilers: e.g. a <em>mORMot</em>'s
<code>RawUTF8</code> field or parameter could be identified as
<code>"typeCS":"string"</code> or <code>"typeJava":"String"</code> in addition
to <code>"typeDelphi":"RawUTF8"</code> and
<code>"typePascal":"string"</code>.</p>
<h3>Publishing the code generator</h3>
<p>By default, and for security reasons, the code generation is not embedded to
your <em>mORMot</em> RESTful server. In fact, the
<code>mORMotWrapper.pas</code> unit will link both <code>mORMot.pas</code> and
<code>SynMustache.pas</code> units, and use <em>Mustache</em> templates to
generate code for a given <code>TSQLRestServer</code> instance.</p>
<p>We will start from the interface-based service <em>Sample code</em> as
defined in the<br />
"<code>SQLite3- Interface based services</code>" folder.<br />
After some minor modifications, we copied the server source code into<br />
"<code>SQLite3- CrossPlatform
ClientsProject14ServerHttpWrapper.dpr</code>":</p>
<pre>
<strong>program</strong> Project14ServerHttpWrapper;
<br /><em>{$APPTYPE CONSOLE}</em>
<br /><strong>uses</strong>
SysUtils,
Classes,
SynCommons,
mORMot,
mORMotHttpServer,
<span style="background-color:yellow;">mORMotWrappers,</span>
Project14Interface <strong>in</strong> '..\14 - Interface based services\Project14Interface.pas';
<br /><strong>type</strong>
TServiceCalculator = <strong>class</strong>(TInterfacedObject, ICalculator)
<strong>public</strong>
<strong>function</strong> Add(n1,n2: integer): integer;
<strong>end</strong>;
<br /><strong>function</strong> TServiceCalculator.Add(n1, n2: integer): integer;
<strong>begin</strong>
result := n1+n2;
<strong>end</strong>;
<br /><strong>var</strong>
aModel: TSQLModel;
aServer: TSQLRestServer;
aHTTPServer: TSQLHttpServer;
<strong>begin</strong>
<em>// create a Data Model</em>
aModel := TSQLModel.Create([],ROOT_NAME);
<strong>try</strong>
<em>// initialize a TObjectList-based database engine</em>
aServer := TSQLRestServerFullMemory.Create(aModel,'test.json',false,true);
<strong>try</strong>
<span style="background-color:yellow;"><em>// add the http://localhost:888/root/wrapper code generation web page</em></span>
<span style="background-color:yellow;">AddToServerWrapperMethod(aServer,</span>
<span style="background-color:yellow;">['..\..\..\CrossPlatform\templates','..\..\..\..\CrossPlatform\templates']);</span>
<em>// register our ICalculator service on the server side</em>
aServer.ServiceRegister(TServiceCalculator,[TypeInfo(ICalculator)],sicShared);
<em>// launch the HTTP server</em>
aHTTPServer := TSQLHttpServer.Create(PORT_NAME,[aServer],'+',useHttpApiRegisteringURI);
<strong>try</strong>
aHTTPServer.AccessControlAllowOrigin := '*'; <em>// for AJAX requests to work</em>
writeln(#10'Background server is running.');
writeln('You can test http://localhost:',PORT_NAME,'/wrapper');
writeln(#10'Press [Enter] to close the server.'#10);
readln;
<strong>finally</strong>
aHTTPServer.Free;
<strong>end</strong>;
<strong>finally</strong>
aServer.Free;
<strong>end</strong>;
<strong>finally</strong>
aModel.Free;
<strong>end</strong>;
<strong>end</strong>.
</pre>
<p>As you can see, we just added a reference to the <code>mORMotWrappers</code>
unit, and a call to <code>AddToServerWrapperMethod()</code> in order to publish
the available code generators.</p>
<p>Now, if you run the <code>Project14ServerHttpWrapper</code> server, and
point your favorite browser to <code>http://localhost:888/root/wrapper</code>
you will see the following page:</p>
<blockquote>
<p><strong>Client Wrappers</strong></p>
<p><strong>Available Templates:</strong></p>
<p>* <strong>CrossPlatform<br /></strong><em>mORMotClient.pas</em> -
<ins>download as file</ins> - <ins>see as text</ins> - <ins>see
template</ins></p>
<p>* <strong>Delphi<br /></strong><em>mORMotClient.pas</em> - <ins>download as
file</ins> - <ins>see as text</ins> - <ins>see template</ins></p>
<p>* <strong>SmartMobileStudio<br /></strong><em>mORMotClient.pas</em> -
<ins>download as file</ins> - <ins>see as text</ins> - <ins>see
template</ins></p>
<p>You can also retrieve the corresponding <ins>template context</ins>.</p>
</blockquote>
<p>Each of the <code>*.mustache</code> template available in the specified
folder is listed here. Links above will allow downloading a client source code
unit, or displaying it as text in the browser. The template can also be
displayed un-rendered, for reference. As true <em>Mustache</em> templates, the
source code files are generated from a <em>data context</em>, which can be
displayed, as JSON, from the "<ins>template context</ins>" link. It may help
you when debugging your own templates. Note that if you modify and save a
<code>.mustache</code> template file, just re-load the "<ins>see as text</ins>"
browser page and your modification is taken in account immediately (you do not
need to restart the server).</p>
<p>Generated source code will follow the template name, and here will always be
downloaded as <code>mORMotClient.pas</code>. Of course, you can change the unit
name for your end-user application. It could be even mandatory if the same
client would access to several <em>mORMot</em> servers at once, which could be
the case in a <em>Service-Oriented Architecture (SOA)</em> project.</p>
<p>Just ensure that you will never change the <code>mORMotClient.pas</code>
generated content by hand. If necessary, you can create and customize your own
<em>Mustache</em> template, to be used for your exact purpose. By design, such
automated code generation will require to re-create the client unit each time
the server ORM or SOA structure is modified. In fact, as stated in the
<code>mORMotClient.pas</code> comment, any manual modification of this file may
be lost after regeneration. You have been warned!</p>
<p>If you feel that the current templates have some issues or need some
enhancements, you are very welcome to send us your change requests on our
forums. Once you are used at it, <em>Mustache</em> templates are fairly easy to
work with. Similarly, if you find out that some information is missing in the
generated <em>data context</em>, e.g. for a new platform or language, we would
be pleased to enhance the official <code>mORMotWrapper.pas</code> process.</p>
<h2>Visit our source code repository</h2>
<p>There is nothing better than some real files.</p>
<p>You can take a look at the following files in our source code
repository:</p>
<ul>
<li>The <a href="https://github.com/synopse/mORMot/tree/master/CrossPlatform/templates"><em>.mustache</em>
template files</a>;</li>
<li>The <em><a href="https://github.com/synopse/mORMot/tree/master/CrossPlatform">SynCrossPlatform*.pas</a></em>
units;</li>
<li>A generated <em><a href="https://github.com/synopse/mORMot/blob/master/SQLite3/Samples/27%20-%20CrossPlatform%20Clients/mORMotClient.pas">
mORMotClient.pas</a></em> unit for <em>Delphi</em> / <em>FPC</em> -
generated from <em><a href="https://github.com/synopse/mORMot/tree/master/SQLite3/Samples/27%20-%20CrossPlatform%20Clients">
RegressionTestsServer.dpr</a></em>;</li>
<li>The same <em><a href="https://github.com/synopse/mORMot/blob/master/SQLite3/Samples/29%20-%20SmartMobileStudio%20Client/mORMotClient.pas">
mORMotClient.pas</a></em> unit for <em>Smart Mobile Studio</em> - also
generated from <em><a href="https://github.com/synopse/mORMot/tree/master/SQLite3/Samples/27%20-%20CrossPlatform%20Clients">RegressionTestsServer.dpr</a></em>;</li>
<li>Another simplier <a href="https://github.com/synopse/mORMot/blob/master/SQLite3/Samples/27%20-%20CrossPlatform%20Clients/SmartMobileStudio/mORMotClient.pas">
<em>mORMotClient.pas</em></a> unit for <em>Smart Mobile Studio</em> - generated
from <em><a href="https://github.com/synopse/mORMot/blob/master/SQLite3/Samples/27%20-%20CrossPlatform%20Clients/Project14ServerHttpWrapper.dpr">
Project14ServerHttpWrapper.dpr</a></em>.</li>
</ul>
<div>Enjoy!</div>Cross-Platform mORMot Clients - Units and Platformsurn:md5:150a3105836e67fd082f8e9ff8032f532014-08-11T20:40:00+02:002014-08-12T08:50:34+02:00AB4327-GANDImORMot FrameworkAJAXblogCrossPlatformDelphiDocumentationDTOFireMonkeyFreePascalHTMLHTTPHTTPSiOSJavaScriptJSONModelmORMotMustachenativeNextGenOpenSourceORMperformancerecordRestSOASynopseUserInterfaceweb<p>Current version of the main framework units target only <em>Win32</em> and
<em>Win64</em> systems.</p>
<p>It allows to make easy self-hosting of <em>mORMot</em> servers for local
business applications in any corporation, or pay cheap hosting in the Cloud,
since <em>mORMot</em> CPU and RAM expectations are much lower than a regular
<code>IIS-WCF-MSSQL-.Net</code> stack.<br />
But in a <em>Service-Oriented Architecture (SOA)</em>, you would probably need
to create clients for platforms outside the <em>Windows</em> world, especially
mobile devices.<br />
<img src="https://blog.synopse.info?post/public/mORMot/CrossPlatform.jpg" alt="" title="CrossPlatform, Aug 2014" /></p>
<p>A set of cross-platform client units is therefore available in the
<code>CrossPlatform</code> sub-folder of the source code repository. It allows
writing any client in modern <em>object pascal</em> language, for:</p>
<ul>
<li>Any version of <em>Delphi</em>, on any platform (<em>Mac OSX</em>, or any
mobile supported devices);</li>
<li><em>FreePascal</em> Compiler 2.7.1;</li>
<li><em>Smart Mobile Studio</em> 2.1, to create AJAX or mobile applications
(via <em>PhoneGap</em>, if needed).</li>
</ul>
<p>This series of articles will introduce you to <em>mORMot</em>'s
Cross-Platform abilities:</p>
<ul>
<li><a href="https://blog.synopse.info?post/post/2014/08/11/CrossPlatform-Clients/Units-Platforms">Units and
platforms</a>;</li>
<li><a href="https://blog.synopse.info?post/post/2014/08/11/CrossPlatform-Clients/Generate-Code">Generating
the client code wrappers</a>;</li>
<li><a href="https://blog.synopse.info?post/post/2014/08/11/CrossPlatform-Clients/Delphi-FreePascal"><em>Delphi</em> /
<em>FreePascal</em> clients</a>;</li>
<li><a href="https://blog.synopse.info?post/post/2014/08/11/CrossPlatform-Clients/SmartMobileStudio"><em>Smart Mobile
Studio</em> clients</a>.</li>
</ul>
<p>Any feedback is <a href="http://synopse.info/forum/viewtopic.php?id=1939">welcome in our forum</a>, as
usual!</p> <h2>Involved units</h2>
The units are the following:
<table>
<tbody>
<tr>
<td><strong>Unit Name</strong></td>
<td><strong>Description</strong></td>
</tr>
<tr>
<td><code>SynCrossPlatformREST.pas</code></td>
<td>Main unit, implementing secured ORM and SOA RESTful client</td>
</tr>
<tr>
<td><code>SynCrossPlatformCrypto.pas</code></td>
<td>SHA-256 and crc32 algorithms, used for authentication</td>
</tr>
<tr>
<td><code>SynCrossPlatformJSON.pas</code></td>
<td>Optimized JSON process (not used by <em>Smart</em>)</td>
</tr>
<tr>
<td><code>SynCrossPlatformSpecific.pas</code></td>
<td>System-specific functions, e.g. HTTP clients</td>
</tr>
</tbody>
</table>
<p>This set of units will provide a solid and shared ground for the any kind of
clients:</p>
<ul>
<li>Connection to a <em>mORMot</em> server via HTTP, with full REST
support;</li>
<li>Support of weak or default authentication to secure the transfer - see
<em><a href="https://blog.synopse.info?post/post/2013/06/07/Authentication-and-Authorization">Authentication</a></em>;</li>
<li>Definition of the <code>TSQLRecord</code> class, using RTTI when available
on <em>Delphi</em> or <em>FreePascal</em>, and generated code for <em>Smart
Mobile Studio</em>;</li>
<li>Remote CRUD operations, via JSON and REST, with a
<code>TSQLRestClientURI</code> class, with the same methods as with the
<code>mORMot.pas</code> framework unit;</li>
<li>Optimized <code>TSQLTableJSON</code> class to handle a JSON result table,
as returned by <em>mORMot</em>'s REST server ORM - see <em><a href="https://blog.synopse.info?post/post/2010/07/02/JSON-format-of-a-RESTful-application">JSON (not) expanded
layouts</a></em>;</li>
<li>Batch process - see <em>BATCH sequences for adding/updating/deleting
records</em> - for transactional and high-speed writes;</li>
<li><em>Client-Server services via methods</em> with parameters
marshaling;</li>
<li><em>Client-Server services via interfaces</em> with parameters marshaling
and instance-life time;</li>
<li>Mapping of most supported field types, including e.g. ISO 8601 date/time
encoding, BLOBs and <code>TModTime</code>/<code>TCreateTime</code> - see
<em>TSQLRecord fields definition </em>in the SAD 1.18 pdf;</li>
<li>Complex <code>record</code> types are also exported and consumed via JSON,
on all platforms (for both ORM and SOA methods);</li>
<li>Some cross-platform low-level functions and types definitions, to help
share as much code as possible between your projects.</li>
</ul>
<p>In the future, C# or Java clients may be written.<br />
The <code>CrossPlatform</code> sub-folder code could be used as reference, to
write minimal and efficient clients on any platform. Our REST model is pretty
straightforward and standard, and use of JSON tends to leverage a lot of
potential marshaling issues which may occur with XML or binary formats.</p>
<p>In practice, a code generator embedded in the <em>mORMot</em> server can be
used to create the client wrappers, using the <em>Mustache template engine</em>
included on the server side. With a click, you can generate and download a
client source file for any supported platform. A set of <code>.mustache</code>
templates is available, and can be customized or extended to support any new
platform: any help is welcome, especially for targeting Java or C# clients.</p>
<h2>Available client platforms</h2>
<h3>Delphi FMX / FreePascal FCL cross-platform support</h3>
<p>Latest versions of <em>Delphi</em> include the <em>FireMonkey</em> (FMX)
framework, able to deliver multi-device, true native applications for
<em>Windows</em>, <em>Mac OSX</em>, <em>Android</em> and <em>iOS</em>
(<em>iPhone</em>/<em>iPad</em>).<br />
Our <code>SynCrossPlatform*</code> units are able to easily create clients for
those platforms.</p>
<p>Similarly, these units can be compiled with FreePascal, so that any
<em>mORMot</em> server could be consumed from the numerous supported platforms
of this compiler.</p>
<p>In order to use those units, ensure in your IDE that the
<code>CrossPlatform</code> sub-folder of the <em>mORMot</em> source code
repository is defined in your <em>Library Search Path</em>.</p>
<h3>Cross-platform JSON</h3>
<p>We developed our own cross-platform JSON process unit in
<code>SynCrossPlatformJSON.pas</code>, shared with <em>Delphi</em> and
<em>FreePascal</em>.<br />
In fact, it appears to be easier to use (since it is <code>variant</code>-based
and with <em>late-binding</em> abilities) and run much faster than the official
<code>DBXJSON.pas</code> unit shipped with latest versions of <em>Delphi</em>,
as stated by the "<code>25 - JSON performance</code>" sample:</p>
<pre>
2.2. Table content:
- Synopse crossplatform: 41,135 assertions passed 20.56ms 400,048/s 1.9 MB
- DBXJSON: 41,136 assertions passed 240.84ms 34,159/s 9.9 MB
</pre>
<p>Our <code>TSQLTableJSON</code> class is more than 10 times faster than
standard <code>DBXJSON</code> unit, when processing a list of results as
returned by a <em>mORMot</em> server.<br />
The latest value on each line above is the memory consumption. It should be of
high interest on mobile platforms, where memory allocation tends to be much
slower and sensitive than on Windows (where <em>FastMM4</em> memory manager
does wonders). Our unit consumes 5 times less memory than the RTL's
version.</p>
<p>We did not include <em>XSuperObject</em> here, which is cross-platform, but
performs even worse than <code>DBXJSON</code> in terms of speed. Other
libraries - as <em>SuperObject</em> or <em>dwsJSON</em> - are not
cross-platform.<br />
See <a href="http://blog.synopse.info/post/json-benchmark-delphi-mormot-superobject-dwsjson-dbxjson">
http://blog.synopse.info/post/json-benchmark-delphi-mormot-superobject-dwsjson-dbxjson</a>
for details about this comparison.</p>
<p>A special mention is due to <em>dwsJSON</em>, which performs very well, but
only on Windows, and is slower than <em>mORMot</em>'s implementation:</p>
<pre>
- Synopse ORM loop: 41,135 assertions passed 6.18ms 1,330,153/s 1.1 MB
- Synopse ORM list: 41,135 assertions passed 6.47ms 1,270,775/s 952 KB
- Synopse crossplatform: 41,135 assertions passed 20.56ms 400,048/s 1.9 MB
- Super object properties: 41,136 assertions passed 2.20s 3,739/s 6.3 MB
- dwsJSON: 41,136 assertions passed 32.05ms 256,628/s 4.7 MB
- DBXJSON: 41,136 assertions passed 240.84ms 34,159/s 9.9 MB
</pre>
<p>The "<code>Synopse ORM</code>" lines stand for the
<code>TSQLTableJSON</code> class as implemented in <code>mORMot.pas</code>. It
uses our optimized UTF-8 functions and classes, in-place escaping together with
our <code>RawUTF8</code> custom string type, so that it is 3 times faster than
our cross-platform units, and 40 times than <code>DBXJSON</code>, using much
less memory. Some tricks used by <code>Synopse ORM</code> rely on pointers and
are not compatible with the <em>NextGen</em> compiler or the official
<em>Delphi</em> road-map, so the <code>Synopse crossplatform</code> uses
diverse algorithm, but offers still pretty good performance.</p>
<p>This unit features a <code>TJSONVariantData</code> custom variant type,
similar to <em>TDocVariant custom variant type</em>, available in the main
<em>mORMot</em> framework.<br />
It allows writing such nice and readable code, with late-binding:</p>
<pre>
<strong>var</strong> doc: <strong>variant</strong>;
json,json2: <strong>string</strong>;
...
doc := JSONVariant('{"test":1234,"name":"Joh\"n\r","zero":0.0}');
assert(doc.test=1234);
assert(doc.name='Joh"n'#13);
assert(doc.name2=null);
assert(doc.zero=0);
json := doc; <em>// conversion to JSON text when assigned to a string variable</em>
assert(json='{"test":1234,"name":"Joh\"n\r","zero":0}');
doc.name2 := 3.1415926;
doc.name := 'John';
json := doc;
assert(json='{"test":1234,"name":"John","zero":0,"name2":3.1415926}');
</pre>
<p>The unit is also able to serialize any <code>TPersistent</code> class, i.e.
all published properties could be written or read from a JSON object
representation. It also handles nested objects, stored as
<code>TCollection</code>.<br />
See for instance in the <code>SynCrossPlatformTests</code> unit:</p>
<pre>
<strong>type</strong>
TMainNested = <strong>class</strong>(TCollectionItem)
<strong>private</strong>
fNumber: double;
fIdent: RawUTF8;
<strong>published</strong>
<strong>property</strong> Ident: RawUTF8 <strong>read</strong> fIdent <strong>write</strong> fIdent;
<strong>property</strong> Number: double <strong>read</strong> fNumber <strong>write</strong> fNumber;
<strong>end</strong>;
<br /> TMain = <strong>class</strong>(TPersistent)
<strong>private</strong>
fName: RawUTF8;
fNested: TCollection;
fList: TStringList;
<strong>public</strong>
<strong>constructor</strong> Create;
<strong>destructor</strong> Destroy; <strong>override</strong>;
<strong>published</strong>
<strong>property</strong> Name: RawUTF8 <strong>read</strong> fName <strong>write</strong> fName;
<strong>property</strong> Nested: TCollection <strong>read</strong> fNested;
<strong>property</strong> List: TStringList <strong>read</strong> fList;
<strong>end</strong>;
<br /> obj1 := TMain.Create;
obj2 := TMain.Create;
...
obj1.Name := IntToStr(i);
item := obj1.Nested.Add <strong>as</strong> TMainNested;
item.Ident := obj1.Name;
item.Number := i/2;
obj1.list.Add(obj1.Name);
json := ObjectToJSON(obj1);
<strong>if</strong> i=1 <strong>then</strong>
assert(json='{"Name":"1","Nested":[{"Ident":"1","Number":0.5}],"List":["1"]}');
JSONToObject(obj2,json);
assert(obj2.Nested.Count=i);
json2 := ObjectToJSON(obj2);
assert(json2=json);
...
</pre>
<p>Of course, this serialization feature is used for the
<code>TSQLRecord</code> ORM class.</p>
<p>Due to lack of RTTI, <code>record</code> serialization is supported via some
functions generated by the server with the code wrappers.</p>
<h3>Delphi OSX and NextGen</h3>
<p>In order to be compliant with the <a href="https://blog.synopse.info?post/post/2013/05/11/Delphi-XE4-NextGen-compiler-is-disapointing"><em>NextGen</em></a> revision,
our <code>SynCrossPlatform*</code> units follow the expectations of this new
family of cross-compilers, which targets <em>Android</em> and
<em>iOS</em>.<br />
In particular, we rely only on the <code>string</code> type for text process
and storage, even at JSON level, and we tried to make object allocation
ARC-compatible. Some types have been defined, e.g. <code>THttpBody</code>,
<code>TUTF8Buffer</code> or <code>AnsiChar</code>, to ensure that our units
would compile on all supported platforms.</p>
<p>On <em>Delphi</em>, the <em>Indy</em> library is used for HTTP requests. It
is cross-platform by nature, so should work on any supported system. For SSL
support with <em>iOS</em> and <em>Android</em> clients, please follow
instructions at <a href="http://blog.marcocantu.com/blog/using_ssl_delphi_ios.html">http://blog.marcocantu.com/blog/using_ssl_delphi_ios.html</a>
you may also download the needed <code>libcrypto.a</code> and
<code>libssl.a</code> files from <a href="http://indy.fulgan.com/SSL/OpenSSLStaticLibs.7z">http://indy.fulgan.com/SSL/OpenSSLStaticLibs.7z</a></p>
<p>Feedback is needed for the mobile targets, via FMX.<br />
In fact, we rely for our own projects on <em>Smart Mobile Studio</em> for our
mobile applications, so the <em>Synopse</em> team did not test <em>Delphi
NextGen</em> platforms (i.e. <em>iOS</em> and <em>Android</em>) as deep as
other systems. Your input would be very valuable and welcome, here!</p>
<h3>FreePascal clients</h3>
<p><code>SynCrossPlatform*</code> units support the <em>FreePascal</em>
Compiler, in its 2.7.1 revision.<br />
Most of the code is shared with <em>Delphi</em>, including RTTI support and all
supported types.</p>
<p>Some restrictions apply, though.</p>
<p>Due to a bug in <em>FreePascal</em> implementation of <code>variant</code>
late binding, the following code won't work as expected:</p>
<pre>
doc.name2 := 3.1415926;
doc.name := 'John';
</pre>
<p>Under <em>FreePascal</em>, you have to write:</p>
<pre>
TJSONVariantData(doc)['name2'] := 3.1415926;
TJSONVariantData(doc)['name'] := 'John';
</pre>
<p>In fact, the way late-binding properties are implemented in the
<em>FreePascal</em> RTL forbid to modify the content of the associated
<code>variant</code>. A private copy of the <code>variant</code> is made, which
is not only slower, but disallows modification of its stored value.<br />
Any feedback and help from the <em>FreePascal</em> maintainers may be
welcome!</p>
<p>As a result, direct access to <code>TJSONVariantData</code> instances, and
not a <code>variant</code> variable, would be faster and less error-prone when
using this compiler, until the issue is fixed.</p>
<p>Another issue with the 2.7.1 revision is how the new <code>string</code>
type is implemented.<br />
In fact, if you use a string variable containing an UTF-8 encoded text, then
the following line would reset the result code page to the system code
page:</p>
<pre>
<strong>function</strong> StringToJSON(<strong>const</strong> Text: <strong>string</strong>): <strong>string</strong>;
...
result := '"'+copy(Text,1,j-1); <em>// here FPC 2.7.1 erases UTF-8 encoding</em>
...
</pre>
<p>It sounds like if <code>'"'</code> will force the code page of
<code>result</code> to be not an UTF-8 content.<br />
With <em>Delphi</em>, this kind of statements work as expected, even for
<code>AnsiString</code> values, and <code>'"'</code> constant is handled as
<code>RawByteString</code>. We were not able to find an easy and safe
workaround for FPC yet. Input is welcome in this area, from any expert!</p>
<p>You have to take care of this limitation, if you target the <em>Windows</em>
operating system with FPC (and <em>Lazarus</em>). Under other systems, the
default code page is likely to be UTF-8, so in this case our
<code>SynCrossPlatform*</code> units will work as expected.</p>
<p>We found out the <em>FreePascal</em> compiler to work very well, and result
in small and fast executables. For most common work, timing is comparable with
<em>Delphi</em>. The memory manager is less optimized than <em>FastMM4</em> for
rough simple threaded tests, but is cross-platform and much more efficient in
multi-thread mode: in fact, it has no giant lock, as <em>FastMM4</em>
suffers.</p>
<h2>Smart Mobile Studio support</h2>
<p><em>Smart Mobile Studio</em> - see <a href="http://www.smartmobilestudio.com">http://www.smartmobilestudio.com</a> - is a
complete RAD environment for writing cutting edge HTML5 mobile applications. It
ships with a fully fledged compiler capable of compiling <em>Object Pascal</em>
(in a modern dialect call <em>SmartPascal</em>) into highly optimized and raw
<em>JavaScript</em>.</p>
<p>There are several solutions able to compile to <em>JavaScript</em>.<br />
In fact, we can find several families of compilers:</p>
<ul>
<li><em>JavaScript</em> super-sets, adding optional <em>strong typing</em>, and
classes, close to the <em>ECMAScript Sixth Edition</em>: the current main
language in this category is certainly <em><a href="http://www.typescriptlang.org/">TypeScript</a></em>, designed by Anders
Hejlsberg (father of both the <em>Delphi</em> language and <em>C#</em>), and
published by <em>Microsoft</em>;</li>
<li>New languages, dedicated to make writing <em>JavaScript</em> programs
easier, with an alternative syntax and new concepts (like classes, lambdas,
scoping, splats, comprehensions...): most relevant languages of this family are
<em><a href="http://coffeescript.org/">CoffeeScript</a></em> and <em><a href="https://www.dartlang.org/">Dart</a></em>;</li>
<li>High-level languages, like <em><a href="http://www.gwtproject.org/">Google
Web Toolkit</a></em> (compiling <em>Java</em> code), <em><a href="http://jsil.org/">JSIL</a></em> (from <em>C#</em> via <em>Mono</em>), or
<em><a href="http://smartmobilestudio.com/">Smart Mobile Studio</a></em> (from
<em>object pascal</em>);</li>
<li>Low-level languages, like <em><a href="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Emscripten">Emscripten</a></em>
(compiling C/C++ from LLVM byte-code, using <em>asm.js</em>).</li>
</ul>
<p>Of course, from our point of view, use of modern <em>object pascal</em> is
of great interest, since it will leverage our own coding skills, and make us
able to share code between client and server sides.</p>
<h3>Beyond JavaScript</h3>
<p>The so-called <em><a href="http://en.wikipedia.org/wiki/The_Smart_Pascal_programming_language">Smart
Pascal</a></em> language brings strong typing, true OOP to <em>JavaScript</em>,
including classes, partial classes, interfaces, inheritance, polymorphism,
virtual and abstract classes and methods, helpers, closures, lambdas,
enumerations and sets, getter/setter expressions, operator overloading,
contract programming. But you can still unleash the power of
<em>JavaScript</em> (some may say "the good parts"), if needed: the
<code>variant</code> type is used to allow dynamic typing, and you can write
some <em>JavaScript</em> code as an <code>asm .. end</code> block.<br />
See <a href="http://en.wikipedia.org/wiki/The_Smart_Pascal_programming_language">http://en.wikipedia.org/wiki/The_Smart_Pascal_programming_language</a></p>
<p>The resulting HTML5 project is self-sufficient with no external
<em>JavaScript</em> library, and is compiled as a single
<code>index.html</code> file (including its <code>css</code>, if needed). The
<em>JavaScript</em> code generated by the compiler (written in <em>Delphi</em>
by Eric Grange), is of very high quality, optimized for best execution
performance (either in JIT or V8), has low memory consumption, and can be
compressed and/or obfuscated.</p>
<p>The <code>SmartCL</code> runtime library encapsulate HTML5 APIs in a set of
pure pascal classes and functions, and an IDE with an integrated form designer
is available. You can debug your application directly within the IDE (since
revision 2.1 - even if it is not yet always stable) or within your browser (IE,
Chrome or FireBug have great debuggers), with step-by-step execution of the
object pascal code (if you define "<em>Add source map (for debugging)</em>" in
<code>Project Options</code> / <code>Linker</code>).</p>
<p>Using a third-party tool like <em>PhoneGap</em> - see <a href="http://phonegap.com">http://phonegap.com</a> - you would be able to supply
your customers with true native <em>iOS</em> or <em>Android</em> applications,
running without any network, and accessing the full power of any modern
<em>Smart Phone</em>. Resulting applications will be much smaller in size than
the one generated by <em>Delphi</em> FMX (a simple <em>Smart</em> RESTful
client with a login form and ORM + SOA tests is zipped as 40 KB), and will work
seamlessly on all HTML5 platforms, including most mobile (like Windows Phone,
Blackberry, Firefox OS, or webOS) or desktop (Windows, Linux, BSD, MacOS)
architectures.</p>
<p><em>Smart Mobile Studio</em> is therefore a great platform for implementing
rich client-side AJAX or <em>Mobile</em> applications, to work with our
client-server <em>mORMot</em> framework.</p>
<h3>Using Smart Mobile Studio with mORMot</h3>
<p>There is no package to be installed within the <em>Smart Mobile Studio</em>
IDE. The client units will be generated directly from the <em>mORMot</em>
server.<br />
Any edition of <em>Smart</em> - see <a href="http://smartmobilestudio.com/feature-matrix">http://smartmobilestudio.com/feature-matrix</a>
- is enough: you do not need to pay for the <em>Enterprise</em> edition to
consume <em>mORMot</em> services. But of course, the <em>Professionnal</em>
edition is recommended, since the <em>Basic</em> edition does not allow to
create forms from the IDE, which is the main point for an AJAX application.</p>
<p>In contrast to the wrappers available in the
<em>Entreprise </em>edition of Smart, for accessing <em>RemObjects</em> or
<em>DataSnap</em> servers, our <em>mORMot</em> clients are 100% written in the
<em>SmartPascal</em> dialect. There is no need to link an external
<code>.js</code> library to your executable, and you will benefit of the
obfuscation and smart linking features of the Smart compiler.</p>
<p>The only requirement is to copy the <em>mORMot</em> cross-platform units to
your <em>Smart Mobile Studio</em> installation. This can be done in three
<code>copy</code> instructions:</p>
<pre>
xcopy SynCrossPlatformSpecific.pas "c:\ProgramData\Optimale Systemer AS\Smart Mobile Studio\Libraries" /Y
xcopy SynCrossPlatformCrypto.pas "c:\ProgramData\Optimale Systemer AS\Smart Mobile Studio\Libraries" /Y
xcopy SynCrossPlatformREST.pas "c:\ProgramData\Optimale Systemer AS\Smart Mobile Studio\Libraries" /Y
</pre>
<p>You can find a corresponding BATCH file in the <code>CrossPlatform</code>
folder, and in <code>SQLite3- SmartMobileStudio
ClientCopySynCrossPlatformUnits.bat</code>.</p>
<p>In fact, the <code>SynCrossPlatformJSON.pas</code> unit is not used under
<em>Smart Mobile Studio</em>: we use the built-in JSON serialization features
of <em>JavaScript</em>, using <code>variant</code> dynamic type, and the
standard <code>JSON.Stringify()</code> and <code>JSON.Parse()</code>
functions.</p>Software Design, Brook, mORMot, RAD, SOLID and OOPurn:md5:8e8c0000a47376152ec7dd305c60b9e92014-05-30T08:28:00+02:002020-07-03T09:29:59+02:00AB4327-GANDImORMot FrameworkblogBrookCrossPlatformDelphiDomainDrivenFreePascalGoodPracticeinterfacemORMotperformanceRADRestSOASOLIDSynopseweb<p>We got a very <a href="http://synopse.info/forum/viewtopic.php?pid=10852#p10852">instructive
discussion in our forums</a>, with <em>Silvio</em>, the maintainer of the
<a href="https://blog.synopse.info?post/2014/05/30/brookframework.org">Brook Framework</a>.<br />
<em>Brook</em> is a nice framework for writing web applications using <em>Free
Pascal</em>.</p>
<p>It comes to my mind what <em>mORMot</em> can offer.<br />
We did not want to compare the features or say that one framework is better
than the other, but it appeared to me that a lot of object pascal programmers
are tied to 20th century programming model.</p>
<p><img src="http://cre8ivethought.s3.amazonaws.com/images/solid/SOLID-small.jpg" alt="" /></p>
<p>In fact, to embrace the potentials of <em>mORMot</em>, you need to switch
your mind, and enhanced your RAD and OOP background, into 21th century <a href="https://blog.synopse.info?post/post/2011/11/27/SOLID-design-principles">SOLID model</a>.</p> <p>Just say that if you want to implement a known design pattern like <a href="https://blog.synopse.info?post/post/2014/01/04/Domain-Driven-Design%3A-part-1">Domain Driven Design</a>, you
will have all needed bricks available with <em>mORMot</em> to focus on
modeling, and you will have to write much more code with <em>Brook</em>.</p>
<p>"<a href="http://en.wikipedia.org/wiki/Convention_over_configuration">Convention over
configuration</a>" means that web services, HTTP and REST are means, not
goals.<br />
The conventions available in <em>mORMot</em> allow to write some code without
any knowledge of what a GET/POST/PUT is, or how routing is handled.<br />
And if you need to tune the default behavior, you still can.</p>
<p>Silvio get it right: most of the complexity of the <em>mORMot</em> internal
core comes from the "conventional" approach and "abstraction to technical
details".<br />
And, to be honest, the other main feature which included complexity in the
implementation was our goal to performance and multithread friendliness.</p>
<p>What Silvio called "bureaucracy" <a href="http://synopse.info/forum/viewtopic.php?pid=10852#p10852">in his post</a> is
that modern serious coding (e.g. DDD) is to <em>uncouple your logic from the
technical details</em> by which it is implemented.<br />
It is not "bureaucracy", it is 21th century software design.<br />
For instance, your business logic code should be uncoupled from implementation
details like transport, security, persistence, marshaling.</p>
<p>This is all about the SOLID principles, and rely on abstraction.<br />
IMHO interface support, dependency injection, and are <a href="https://blog.synopse.info?post/post/2012/10/14/Interfaces-in-practice%3A-dependency-injection%2C-stubs-and-mocks">
mandatory for modern business project</a>.<br />
So that you can <a href="https://blog.synopse.info?post/post/2012/10/14/Stubs-and-Mocks-for-Delphi-with-mORMot">stub/mock</a> any
part of your application (DB, transport...) and maintain/test it.<br />
It is mandatory for test-driven approach, and serious modern programming.</p>
<p>In short, you have several level of quality coding:</p>
<ul>
<li>RAD approach, which mixes UI and logic/persistence with
<em>components</em>;</li>
<li>OOP approach, which try to uncouple the tiers with <em>classes</em>;</li>
<li>SOLID approach, which rely on abstraction and uncoupling at all levels,
with <em>interfaces</em>.</li>
</ul>
<p>Brook is a clean OOP solution, but not SOLID.</p>
<p>SOLID design may be something new, as it is for most pascal
developers.<br />
If you was involved in serious software development in C# (or Java), as I was,
you are perhaps already familiar with this set of design principles.<br />
You may think SOLID is not worth it, and call it "bureaucracy" or "marketing
stuff".<br />
BTW, this is the reason why there is some mandatory design principles chapters
in the <em>mORMot</em> documentation - take a look at the <a href="http://synopse.info/files/pdf/Synopse%20mORMot%20Framework%20SAD%201.18.pdf">SAD
1.18 pdf</a>.</p>
<p>And what Nick Hodges tried to introduce Delphi programmers in his latest
book, "<a href="https://leanpub.com/codingindelphi">Coding in
Delphi</a>".<br />
I still do not need <em>generics</em> or <em>attributes</em> as
implemented in modern Delphi (which IMHO pollute the code), and find in FPC and
Delphi 7/2007 syntax all that I need to write SOLID code.<br />
But Nick advocates the same principles that drove <em>mORMot</em>'s
architecture, mainly DI and TDD.</p>
<p>I can assure you that SOLID is much more "practical" than regular OOP
design.<br />
My point of view is that <em>mORMot</em> SOLID design let you be much more
productive than a regular OOP design, as offered by <em>Brook</em>.<br />
Just try to write e.g. 10 small SOA services and consume them with clients in
both frameworks, unit test them, and you will find out what I mean...<br />
Thanks to <a href="https://blog.synopse.info?post/post/2012/10/18/Interfaces-are-not-evil">Delphi
<code>interface</code> features</a>, you can write real SOLID code, and still
benefit of object pascal strengths.</p>
<p><em>Brook</em>, and even the current state of the FCL, or even the Delphi
RTL, are just not able to follow the SOLID patterns, out of the box.<br />
They only offer sets of classes, gathered by units, to do amazing thinks.<br />
But you need external tools (like <em><a href="https://bitbucket.org/sglienke/spring4d">Spring4D</a></em> or <em>mORMot</em>)
to directly implement SOLID patterns, i.e. use interfaces with ease (dependency
injection) and safety (weak reference pointers).<br />
If I make <em>mORMot</em> compatible with Free Pascal, I guess it will even add
a lot of features to the FCL, e.g. stubbing/mocking and such.<br />
It could benefit to the community!</p>
<p>Ensure you took a look at the <a href="https://blog.synopse.info?post/post/2014/04/18/Introducing-mORMot-s-architecture-and-design-principles">slides
we shared about all those design principles</a>.<br />
By the way, thanks Bill for the review!</p>
<p>Feedback is <a href="http://synopse.info/forum/viewtopic.php?id=1762">welcome in our forum</a>, as
usual!<br />
Any help for porting <em>mORMot</em> to FPC, and gives us some motivation is
welcome.</p>Introducing mORMot's architecture and design principlesurn:md5:265b0a5927ad96f526d355c5c9aca5322014-04-18T11:40:00+02:002015-01-04T10:10:34+01:00AB4327-GANDImORMot Frameworkcode-firstDatabasedatabase-firstDelphiDependencyInjectionDocumentationDomainDrivenDTOdynamic arrayEventSourcingfactoryFireDACGoodPracticehttp.sysHttpApiHTTPSinterfaceJSONLateBindingmockModelmORMotMSSQLMySQLNextGenNexusDBNoSQLODBCOleDBOpenSourceOracleORMperformancePostgreSQLRepositoryRTTIsessionshardingSOASQLSQLite3SynDBSynopseTDataSetTDocVariantTDynArraytransactionUnicodeUniDACValueObjectVirtualTableWinHTTPWinINetZEOS <p>We have just released a set of slides introducing </p>
<ul>
<li>ORM, SOA, REST, JSON, MVC, MVVM, SOLID, Mocks/Stubs, Domain-Driven Design
concepts with Delphi, </li>
<li>and showing some sample code using our Open Source <em>mORMot</em>
framework.</li>
</ul>
<p>You can follow the <a href="https://drive.google.com/folderview?id=0B0r8u-FwvxWdeVJVZnBhSEpKYkE&usp=sharing">
public link on Google Drive</a>!</p>
<p><img src="http://images.fineartamerica.com/images-medium-large/1-golden-marmot-maureen-ida-farley.jpg" width="450" height="291" alt="" /></p>
<p>This is a great opportunity to discovers some patterns you may not be
familiar with, and find out how <em>mORMot</em> try to implement them.<br />
This set of slides may be less intimidating than our huge documentation - do
not be terrified by our <a href="http://synopse.info/files/html/Synopse%20mORMot%20Framework%20SAD%201.18.html">
Online Documentation</a>!<br />
The <a href="http://synopse.info/files/html/Synopse%20mORMot%20Framework%20SAD%201.18.html#SIDE_TITL_40">
first set of pages</a> (presenting architecture and design principles) is worth
reading.</p>
<p>Feedback is <a href="http://synopse.info/forum/viewtopic.php?id=1707">welcome on our forum, as
usual</a>.</p>Think free as in free speech, not free beerurn:md5:4df8d5b79546157f1d8a83a82e64942e2014-02-28T18:58:00+01:002015-11-16T11:25:07+01:00AB4327-GANDIOpen SourceblogDelphiFreeFreePascalGoodPracticemORMotOpenSourceSourceSynopse<p>After more than 5 years of opening some huge part of Delphi code base, just
my two cents.</p>
<p>Free software means free as a bird.</p>
<p>In practice, most Open Source "consumers" focus on free as a free
beer...<br />
This is a reality, especially for "niche" projects like developing libraries
for Delphi.</p>
<p><img src="http://ecx.images-amazon.com/images/I/51XpdusUVoL._SL400_.jpg" alt="" /></p>
<p>Here are some thoughts from my little experiment with <em>mORMot</em>.<br />
If you ask what Open Source for libraries mean, it may help you!</p> <p>I mixed both PROs and CONs, since it is difficult to make a hierarchy of
thoughts, when you are so involved as I am.</p>
<p>1. Open Source is a great adventure, in which you encounter some very nice
people, and learn from others;</p>
<p>2. Open Source does not give a lot of benefit, neither financial, nor for
fame (do not expect much reward);</p>
<p>3. Select a permissive license (like MPL), and/or a GPL/LGPL license since
it is needed for such viral projects - but I do not know many Delphi software
created as GPL/LGPL;</p>
<p>4. Release soon: do not wait to post the code - when it works (compile +
pass the regression tests), submit it, even if it is not perfect;</p>
<p>5. Release often: use a source code repository, and post every modification
in it;</p>
<p>6. Do not leak the features you need on your side for a particular client -
it smells like wrongly designed code in your libraries, which may not be open
enough for extension;</p>
<p>7. If you publish a library, try to document your code, and follow
coding/naming conventions - if you do not have time for documenting, try at
least some wiki;</p>
<p>8. Have a forum for support, but do not expect other users to help -
usually, only the main contributors will post on the forum - others may feel
too shy;</p>
<p>9. Be gentile and patient with every user (unless he/she is a troll or
openly incompetent), and be enthusiast with any code contribution (if you can,
integrate it ASAP in the trunk);</p>
<p>10. Most of your users will ask for free support, or even debugging of their
own code - learn to say NO;</p>
<p>11. Feedback is welcome, even from people who do not like what you and other
contributors did;</p>
<p>12. If you are several co-workers working with your libraries, let everyone
be involved in support;</p>
<p>13. Have a bug tracking web site, and distinguish bugs from feature
requests;</p>
<p>14. Set priorities to tickets, especially feature requests: implement first
those YOU need, then those you may be paid for, then those you may have fun
working on, then let people contribute on their side for the remaining;</p>
<p>13. Use your public web site to track and discuss any bug or feature request
you encounter on your side about the libraries (it will benefit all);</p>
<p>14. A set of regression tests with good coverage is mandatory;</p>
<p>15. You can offer support for your libraries for money, but you will be only
asked on a few occasions;</p>
<p>16. You will have companies or individuals ignoring your libraries (why was
our blog never accepted in <em>DelphiFeeds</em>? or <a href="http://synopse.info/forum/viewtopic.php?pid=17453#p17453">rejected from
<em>Delphi GetIt</em></a>), perhaps because they do not understand Open Source,
or see it as some "unfair" competition;</p>
<p>17. Do not hide anything, even restrictions nor known issues;</p>
<p>18. Try to make your site appealing, but do not abuse of marketing - good
marketing and wrong code did kill the component market - good code is the
priority;</p>
<p>19. Do your best to support several versions of the Delphi compiler - a lot
of users, especially in Open Source, are still using old (pre-Unicode!)
versions;</p>
<p>20. Design and architecture level of some Delphi user is somewhat low - most
did use the tool in pure RAD, and are afraid or ignorant about modern
programming (like <a href="https://blog.synopse.info?post/post/2014/05/30/Software-Design,-Brook,-mORMot,-RAD,-SOLID-and-OOP">SOLID,
DDD, stubbing, unit testing</a>...);</p>
<p>21. If you incorporate some code from other Open Source project, clearly
state it (good), or rewrite it from scratch (even better - with the
corresponding unit tests);</p>
<p>22. Delphi is a very small market, not trendy, especially for young
developers;</p>
<p>23. Open Source your libraries can be time consuming (e.g. if you are as
perfectionist as I am) - but the main point is about balancing your investment,
with the benefit of sharing;</p>
<p>24. Thanks to a larger base of users, you will find bugs you would never
discover otherwise, but on production (e.g. with Asiatic Windows, or on heavy
production);</p>
<p>25. Participate to the Delphi community outside of your own project(s), e.g.
on <a href="http://stackoverflow.com/">StackOverflow</a> or <a href="https://plus.google.com/u/0/communities/103113685381486591754">Google+</a> -
it will help ranking your web site;</p>
<p>26. Your code will remain for ever on the Internet archives, and you will
never be forgotten;</p>
<p>27. Even your managers can be <a href="https://www.openhub.net/p/mormot/estimated_cost">convinced about the
(financial) benefits of Open Sourcing some code</a>, and also that you may
spend some (identified) part of your time to maintain a community;</p>
<p>28. Sharing is everything.</p>
<p>Feedback is <a href="http://synopse.info/forum/viewtopic.php?id=1638">welcome in our forum</a>!</p>SynProject tool 1.18urn:md5:7a269afdad0718fb9080b4b5247987792013-03-25T22:58:00+01:002013-03-25T23:02:13+01:00AB4327-GANDISynProject documentation and versioningblogDelphiDocumentationGoodPracticemORMotSourceSynopseSynProject <p>We have uploaded an updatetd compiled version of our Open Source
<em>SynProject</em> tool in <a href="http://synopse.info/files/SynProject.zip">SynProject.zip</a>.</p>
<p>Synopse <em>SynProject</em> is an open source application for code source
versioning and automated documentation of software projects.<br />
Licensed under a GPL license.</p>
<p><img src="http://synopse.info/files/synproject/SynProject05.png" width="479" height="353" /></p>
<p>Main feature is a new (better-looking?) template for the generated
files.<br />
See our <a href="http://synopse.info/fossil/wiki?name=Downloads"><em>mORMot</em> framework
documentation</a> for a good sample of rendering content.</p>
<p>The <a href="http://synopse.info/fossil/wiki?name=SynProject">internal wiki
pages</a> related to this tool has also been refreshed.</p>
<p>Feedback is <a href="http://synopse.info/forum/viewtopic.php?id=1180">welcome on our forum</a>!</p>External database speed improvementsurn:md5:3b4ae0230e9cbddb42786d7e95b82abe2013-01-28T08:16:00+01:002013-01-28T08:33:25+01:00AB4327-GANDImORMot Frameworkarray bindingBatchblogDatabaseDelphiJSONmORMotMSSQLODBCOleDBOracleORMperformanceSourceSQLSQLite3SynDBSynopse<p>Some major speed improvements have been made to our <code>SynDB*</code>
units, and how they are used within the <em>mORMot</em> persistence
layer.<br />
It results in an amazing speed increase, in some cases.</p>
<p>Here are some of the optimizations how took place in the source code
trunk:</p>
<ul>
<li>SQL statement <a href="http://synopse.info/forum/viewtopic.php?pid=6359#p6359">client-side cache in
ODBC and OleDB</a>;</li>
<li>SQL statement <a href="http://docs.oracle.com/cd/B28359_01/appdev.111/b28395/oci09adv.htm#i471377">server-side
cache for Oracle</a>;</li>
<li>When inserting individual data rows in an external table, the last inserted
IDs are maintained in memory instead of executing "<em>select max(id)</em>" -
we added a new property <code>EngineAddUseSelectMaxID</code> to unset
this optimization - we noted that this modification circumvented a <a href="http://www.firebirdfaq.org/faq205/">known limitation of Firebird</a> very
efficiently.</li>
</ul>
<p>Overall, I observed from x2 to x10 performance boost with simple
<code>Add()</code> operations, using ODBC, OleDB and direct Oracle access, when
compare to <a href="https://blog.synopse.info?post/post/2012/09/14/Updated-mORMot-benchmarks-on-another-HW-configuration">previous
benchmarks</a> (which were already impressive).<br />
<a href="https://blog.synopse.info?post/post/2011/06/03/BATCH-sequences-for-adding/updating/deleting-records">BATCH
mode performance</a> is less impacted, since it by-passed some of those
limitations, but even in this operation mode, there is some benefits
(especially with ODBC and OleDB).</p>
<p>Here are some results, directly generated by the supplied "<em>15 - External
DB performance</em>" sample.</p> <h3>Insertion speed</h3>
<p>Here we test insertion of some records, for most of our supplied
engines.<br />
We did the test with <code>UNIK</code> conditional undefined, i.e. with no
index of the <code>Name</code> field.</p>
<p>A Core i7 notebook has been used, as hardware platform.<br />
Oracle 11g database is remotely accessed over a corporate network, so latency
and bandwidth is not optimal.<br />
The hardrive is a SSD this time - so we will see how it affects the
results.</p>
<table>
<tbody>
<tr align="center">
<td> </td>
<td><strong>SQLite3<br />
(file full)</strong></td>
<td><strong>SQLite3<br />
(file off)</strong></td>
<td><strong>SQLite3<br />
(mem)</strong></td>
<td><strong>TObjectList<br />
(static)</strong></td>
<td><strong>TObjectList<br />
(virtual)</strong></td>
<td><strong>SQLite3<br />
(ext file full)</strong></td>
<td><strong>SQLite3<br />
(ext file off)</strong></td>
<td><strong>SQLite3<br />
(ext mem)</strong></td>
<td><strong>Oracle</strong></td>
<td><strong>ODBC Oracle</strong></td>
<td><strong>Jet</strong></td>
</tr>
<tr align="center">
<td><strong>Direct</strong></td>
<td>501</td>
<td>911</td>
<td>81870</td>
<td>281848</td>
<td>288234</td>
<td>548</td>
<td>952</td>
<td>72697</td>
<td>518</td>
<td>512</td>
<td>4159</td>
</tr>
<tr align="center">
<td><strong>Batch</strong></td>
<td>523</td>
<td>891</td>
<td>102614</td>
<td>409836</td>
<td>417257</td>
<td>557</td>
<td>868</td>
<td>91617</td>
<td>77155</td>
<td>509</td>
<td>4441</td>
</tr>
<tr align="center">
<td><strong>Trans</strong></td>
<td>90388</td>
<td>95884</td>
<td>96612</td>
<td>279579</td>
<td>286188</td>
<td>99681</td>
<td>70950</td>
<td>105674</td>
<td>1024</td>
<td>1432</td>
<td>4920</td>
</tr>
<tr align="center">
<td><strong>Batch Trans</strong></td>
<td>110869</td>
<td>117376</td>
<td>125190</td>
<td>412813</td>
<td>398851</td>
<td>127424</td>
<td>126627</td>
<td>121368</td>
<td>62601</td>
<td>1019</td>
<td>4926</td>
</tr>
</tbody>
</table>
<p><img src="http://chart.apis.google.com/chart?chtt=Insertion+speed+%28rows%2Fsecond%29&chxl=1:|Batch+Trans|Trans|Batch|Direct&chxt=x,y&chbh=a&chs=600x300&cht=bhg&chco=3D7930,3D8930,309F30,6070F0,5070E0,40C355,65D055,80C1A2,F05050,F0A280&chxr=0,0,417257&chds=0,417257,0,417257,0,417257,0,417257,0,417257&chd=t:501,523,90388,110869|911,891,95884,117376|81870,102614,96612,125190|281848,409836,279579,412813|288234,417257,286188,398851|548,557,99681,127424|952,868,70950,126627|72697,91617,105674,121368|518,77155,1024,62601|512,509,1432,1019|4159,4441,4920,4926&chdl=SQLite3+%28file+full%29|SQLite3+%28file+off%29|SQLite3+%28mem%29|TObjectList+%28static%29|TObjectList+%28virtual%29|SQLite3+%28ext+file+full%29|SQLite3+%28ext+file+off%29|SQLite3+%28ext+mem%29|Oracle|ODBC+Oracle|Jet" /></p>
<p><img src="http://chart.apis.google.com/chart?chtt=Insertion+speed+%28rows%2Fsecond%29&chxl=1:|Jet|ODBC+Oracle|Oracle|SQLite3+%28ext+mem%29|SQLite3+%28ext+file+off%29|SQLite3+%28ext+file+full%29|TObjectList+%28virtual%29|TObjectList+%28static%29|SQLite3+%28mem%29|SQLite3+%28file+off%29|SQLite3+%28file+full%29&chxt=x,y&chbh=a&chs=600x300&cht=bhg&chco=3D7930,3D8930,309F30,6070F0,5070E0,40C355,65D055,80C1A2,F05050,F0A280&chxr=0,0,417257&chds=0,417257,0,417257,0,417257,0,417257,0,417257,0,417257,0,417257,0,417257,0,417257,0,417257,0,417257&chd=t:501,911,81870,281848,288234,548,952,72697,518,512,4159|523,891,102614,409836,417257,557,868,91617,77155,509,4441|90388,95884,96612,279579,286188,99681,70950,105674,1024,1432,4920|110869,117376,125190,412813,398851,127424,126627,121368,62601,1019,4926&chdl=Direct|Batch|Trans|Batch+Trans" /></p>
<p>Performance gain is impressive, especially for "ODBC Oracle" and also "OleDB
Jet".<br />
Since Jet/MSAccess is a local engine, it is faster than Oracle for one record
retrieval - it does not suffer from the network latency. But it is faster than
<em>SQlite3</em> at insertion, due to a multi-thread design - which is perhaps
<a href="http://www.sqlite.org/atomiccommit.html">less ACID</a> nor <a href="http://www.sqlite.org/testing.html">proven</a>.<br />
Note that this hardware configuration run on a SSD, so even "SQLite3 (file
full)" configuration is very much boosted - about 3 times faster.<br />
Our direct Oracle access classes achieve more than 77,000 inserts per second in
BATCH mode (using the Array Binding feature).<br />
Direct <code>TObjectList</code> in-memory engine reaches amazing speed, when
used in BATCH mode - more than 400,000 inserts per second!</p>
<h3>Read speed</h3>
<table>
<tbody>
<tr align="center">
<td> </td>
<td><strong>SQLite3<br />
(file full)</strong></td>
<td><strong>SQLite3<br />
(file off)</strong></td>
<td><strong>SQLite3<br />
(mem)</strong></td>
<td><strong>TObjectList<br />
(static)</strong></td>
<td><strong>TObjectList<br />
(virtual)</strong></td>
<td><strong>SQLite3<br />
(ext file full)</strong></td>
<td><strong>SQLite3<br />
(ext file off)</strong></td>
<td><strong>SQLite3<br />
(ext mem)</strong></td>
<td><strong>Oracle</strong></td>
<td><strong>ODBC Oracle</strong></td>
<td><strong>Jet</strong></td>
</tr>
<tr align="center">
<td><strong>By one</strong></td>
<td>26777</td>
<td>26933</td>
<td>122016</td>
<td>298400</td>
<td>301041</td>
<td>135413</td>
<td>133571</td>
<td>131877</td>
<td>1289</td>
<td>1156</td>
<td>2413</td>
</tr>
<tr align="center">
<td><strong>All Virtual</strong></td>
<td>429331</td>
<td>427423</td>
<td>447227</td>
<td>715717</td>
<td>241289</td>
<td>232385</td>
<td>167420</td>
<td>202839</td>
<td>63473</td>
<td>35029</td>
<td>127772</td>
</tr>
<tr align="center">
<td><strong>All Direct</strong></td>
<td>443773</td>
<td>433463</td>
<td>427094</td>
<td>711035</td>
<td>700574</td>
<td>432189</td>
<td>334179</td>
<td>340136</td>
<td>90184</td>
<td>39485</td>
<td>186164</td>
</tr>
</tbody>
</table>
<p><img src="http://chart.apis.google.com/chart?chtt=Read+speed+%28rows%2Fsecond%29&chxl=1:|All+Direct|All+Virtual|By+one&chxt=x,y&chbh=a&chs=600x300&cht=bhg&chco=3D7930,3D8930,309F30,6070F0,5070E0,40C355,65D055,80C1A2,F05050,F0A280&chxr=0,0,715717&chds=0,715717,0,715717,0,715717&chd=t:26777,429331,443773|26933,427423,433463|122016,447227,427094|298400,715717,711035|301041,241289,700574|135413,232385,432189|133571,167420,334179|131877,202839,340136|1289,63473,90184|1156,35029,39485|2413,127772,186164&chdl=SQLite3+%28file+full%29|SQLite3+%28file+off%29|SQLite3+%28mem%29|TObjectList+%28static%29|TObjectList+%28virtual%29|SQLite3+%28ext+file+full%29|SQLite3+%28ext+file+off%29|SQLite3+%28ext+mem%29|Oracle|ODBC+Oracle|Jet" /></p>
<p><img src="http://chart.apis.google.com/chart?chtt=Read+speed+%28rows%2Fsecond%29&chxl=1:|Jet|ODBC+Oracle|Oracle|SQLite3+%28ext+mem%29|SQLite3+%28ext+file+off%29|SQLite3+%28ext+file+full%29|TObjectList+%28virtual%29|TObjectList+%28static%29|SQLite3+%28mem%29|SQLite3+%28file+off%29|SQLite3+%28file+full%29&chxt=x,y&chbh=a&chs=600x300&cht=bhg&chco=3D7930,3D8930,309F30,6070F0,5070E0,40C355,65D055,80C1A2,F05050,F0A280&chxr=0,0,715717&chds=0,715717,0,715717,0,715717,0,715717,0,715717,0,715717,0,715717,0,715717,0,715717,0,715717,0,715717&chd=t:26777,26933,122016,298400,301041,135413,133571,131877,1289,1156,2413|429331,427423,447227,715717,241289,232385,167420,202839,63473,35029,127772|443773,433463,427094,711035,700574,432189,334179,340136,90184,39485,186164&chdl=By+one|All+Virtual|All+Direct" /></p>
<p>Reading speed was also increased. ODBC results have the biggest
improvement.<br />
Server-side statement cache for Oracle makes individual reading of records 2
times faster. Wow.<br />
The <em>SQLite3</em> engine is still the more reactive SQL database here, when
it comes to reading. <br />
Of course, direct <code>TObjectList</code> engine is pretty fast - more than
700,000 records per second.</p>
<p>Feedback is <a href="http://synopse.info/forum/viewtopic.php?pid=6363#p6363">welcome on our
forum</a>, as usual.</p>Adding JavaScript server-side support to mORMoturn:md5:b5f40146fd76550037974ecba670e7642013-01-20T16:37:00+01:002013-03-28T10:57:54+01:00AB4327-GANDImORMot FrameworkAJAXblogDatabaseDelphiDomainDrivenDTOEntityObjectEventCollaborationEventSourcingForumshttp.sysinterfacemORMotORMSynopsesyntaxTypeScriptValueObject<p>A long-time <em>mORMot</em> user and contributor just made a proposal on our
forums.<br />
He did use <em>mORMot</em> classes to integrate a <em>SpiderMonkey</em>
JavaScript engine to our <a href="https://blog.synopse.info?post/post/2012/11/23/Speed-comparison-between-WCF%2C-Java%2C-DataSnap-and-mORMot">very
fast and scaling HTTP server</a>, including our <a href="https://blog.synopse.info?post/post/2011/06/02/Fast-JSON-parsing">optimized JSON serialization
layer</a>.</p>
<p><img src="http://synopse.info/fossil/raw/SQLite3/Documentation/cartoon02.png?name=25c6b50ae84bcb91ba80fb51f58dd9e2471a8901" alt="" /></p>
<p>Today, he <a href="http://synopse.info/forum/viewtopic.php?id=1044">sent to
me some of his source code</a>, which sounds ready to be included in the main
trunk!</p>
<p>This is a great contribution, and Pavel's goal is nothing less than
offering<br />
<strong>Delphi based, FAST multithreaded server with ORM and node.js modules
compatible</strong>.</p> <p><em>JavaScript</em> can easily fit in a <a href="https://blog.synopse.info?post/post/2013/01/05/Domain-Driven-Design-and-mORMot">Domain Driven Design</a>, at
least at several levels:</p>
<ul>
<li><em>Client layer</em>,to validate input and create custom UI or reports
(could be AJAX or even Delphi client);</li>
<li><em>Application layer</em>, as the application logic should either not be
part of the thin client, but stay on the server, either be
<em>injected</em> into a rich and modular client from the server -
JavaScript everywhere, but we may imagine to create a
<em>mORMot</em> client, as native application, retrieving its business
logic from the server;</li>
<li><em>Application layer</em>, to adapt the common business model to one
customer or a given application (e.g. custom behavior, reporting);</li>
<li><em>Business layer</em>, to define the business logic using the domain
objects (e.g. to adapt to a given change of policy).</li>
</ul>
<p>Therefore, in addition to Pavel's proposal, I would like to make available
two features of <em>mORMot</em> (in addition to other cross-cutting elements of
the framework, like logging, security, session handling, reporting, pdf
generation, cache):</p>
<ul>
<li><a href="https://blog.synopse.info?post/post/2012/03/07/Interface-based-services">Interface-based
services</a>;</li>
<li><a href="https://blog.synopse.info?post/post/2012/09/06/Roadmap%3A-interface-based-callbacks-for-Event-Collaboration">
Event-driven design</a>.</li>
</ul>
<p>Both of these features do make sense in a business model, to customize your
project according to customer expectations, and even let the customer create or
adapt its own application layer to its needs.<br />
It is easy to adapt some existing JavaScript code, so that the report layout
will change, or to apply a new tax or process.</p>
<p>Event-sourcing does also make sense from the JavaScript point of view.<br />
It could be very handy to add an event listener to an existing business core,
then use JavaScript to listen to those events, and define some custom process,
without touching the main business logic.</p>
<p>My only concern, spending a lot of time in Delphi and C# source code, is
that JavaScript, as a language, lacks of interface support and strong
typing<br />
This is why I'm looking (since months) at <a href="https://blog.synopse.info?post/post/2012/04/19/Smart%3A-mORMot%2C-from-Delphi-to-JavaScript">DWS script /
SmartMobileStudio</a> and <a href="http://www.typescriptlang.org">TypeScript</a>.</p>
<p><em>TypeScript</em> is an Open Source scalable JavaScript development with
types, classes and modules. It is published by Microsoft, but is open
source.<br />
It comes with full source code of its compiler in <em>TypeScript</em>, so can
be embedded very easily to our <em>SpiderMonkey</em> modules.<br />
<em>TypeScript</em> is great because it is a layer over JavaScript, adding
<em>strong typing</em> and <em>interfaces</em>.<br />
Other scripts compiling to JavaScript either lack of those two (like <a href="http://coffeescript.org/">CoffeeScript</a> or Opal), use a slow and non
integrated virtual machine (yes, a virtual machine within a virtual machine...
do you follow my mind?), or need a whole SDK to be available (like <a href="http://www.dartlang.org/docs/dart2js/">Dart</a>).</p>
<p>And our little <em>mORMot</em> likes interfaces, efficient and small
solutions.</p>
<p>DWS/SMS script compiler to JavaScript is not free any more. So it is no
option for us (sorry Eric).<br />
I suspect <em>TypeScript</em> could be a great optional syntax for our
<em>mORMot</em> business layer.<br />
Do not forget that Anders Hejlsberg, lead architect of Delphi and C#, has
worked on development of <em>TypeScript</em>!</p>
<p>Feedback is <a href="http://synopse.info/forum/viewtopic.php?id=1044">welcome on our forum</a>.</p>Enhance existing projects with mORMoturn:md5:4c7790dbcbd8b6beb99d8e3708263eb02012-12-31T15:45:00+01:002013-10-14T12:47:06+02:00AB4327-GANDImORMot Frameworkarray bindingCQRSDatabaseDelphidynamic arrayGoodPracticeHTTPinterfaceJSONlogmORMotODBCOleDBOracleORMPDFperformancerecordRestSOASQLSQLite3SynDBSynopseUserInterfaceWCF<p>Even if <em>mORMot</em> will be more easily used in a project designed from
scratch, it fits very well the purpose of evolving any existing Delphi project,
or even creating the server side part of an AJAX application. </p>
<p><img src="https://blog.synopse.info?post/public/mORMot/MarmotHeater.jpg" alt="" title="mOrmot Heater, oct. 2013" /></p>
<p>One benefit of such a framework is to facilitate the transition from a
Client-Server architecture to a N-Tier layered pattern.</p> <p>Due to its modular design, you can integrate some framework bricks to your
existing application:</p>
<ul>
<li>You may add <a href="https://blog.synopse.info?post/post/2011/04/14/Enhanced-logging-in-SynCommons">logging</a> to your code, to
track issues and customer-side profiling;</li>
<li>Use low-level classes like <a href="https://blog.synopse.info?post/post/2011/03/12/TDynArray-and-Record-compare/load/save-using-fast-RTTI">record
or dynamic array wrappers</a>, including JSON or binary persistence;</li>
<li>You can use the <a href="https://blog.synopse.info?post/post/2011/06/27/SynOleDB%3A-OpenSource-Unit-for-direct-access-to-any-database-via-OleDB">
direct DB layers</a>, including the <code>TQuery</code> emulation class, to
replace some BDE queries, or introduce nice unique features like direct
database access or <a href="https://blog.synopse.info?post/post/2012/07/19/Oracle-Array-Binding-and-BATCH-performance">array binding</a>
for very fast data insertion; </li>
<li>Reports could benefit of the <code>mORMotReport.pas</code> <a href="https://blog.synopse.info?post/post/2010/06/30/Making-report-from-code">code-based system</a>, which is very
easy to use even on the server side (serving PDF files), when your business
logic heavily relies on objects, not direct DB ; </li>
<li>HTTP request may be made available using <a href="https://blog.synopse.info?post/post/2010/07/18/DataSnap-like-Client-Server-JSON-RESTful-Services-in-Delphi-7-2010">
Client-Server services via methods</a>, e.g. for retrieving HTML pages
generated on the fly, pictures or PDF reports; </li>
<li>You can little by little move your logic out of the client side code into
some <a href="https://blog.synopse.info?post/post/2012/03/07/Interface-based-services">server services
defined via interfaces</a>, without the overhead of SOAP or WCF; migrating into
SOA is IMHO the main benefit of <em>mORMot</em> for existing projects;</li>
<li>Make your application ready to offer a RESTful interface, e.g. for
consuming JSON content via AJAX or mobile clients;</li>
<li>New tables may be defined via the <a href="https://blog.synopse.info?post/post/2012/09/10/Don-t-be-confused-by-our-little-mORMot-%21">ORM features of
<em>mORMot</em></a>, still hosted in your external SQL server, as any previous
data; in particular, mixed pure-ORM and regular-SQL requests may
coexist; </li>
<li>You may benefit from our very fast in-memory engine, a dedicated
<em>SQLite3</em>-based consolidation database or even the caching features,
shared on the server side, when <a href="https://blog.synopse.info?post/post/2012/09/14/Updated-mORMot-benchmarks-on-another-HW-configuration">performance
is needed</a> - it may help integrating some <a href="http://msdn.microsoft.com/en-us/library/jj554200.aspx">CQRS pattern</a>
(<em>Command Query Responsibility Segregation</em>) into your application via a
RESTful interface, and delegate some queries from your main database;</li>
<li>If you are still using an old version of Delphi, and can't easily move up
due to some third party components or existing code base, <em>mORMot</em> will
offer all the needed features to start ORM, N-Tier and SOA, starting with a
Delphi 6 edition;</li>
<li>Once your application reached success, you start to understand that you
need to enhance your architectural model, then start thinking about the benefit
of <a href="https://blog.synopse.info?post/post/2013/01/05/Domain-Driven-Design-and-mORMot">embracing
Domain-Driven-Design</a>.</li>
</ul>
<p><em>mORMot</em> implements the needed techniques for introducing what
Michael Feathers calls, in his book <em>Working Effectively With Legacy
Code</em>, a seam.<br />
A seam is an area where you can start to cleave off some legacy code and begin
to introduce changes. Even <a href="https://blog.synopse.info?post/post/2012/10/14/Stubs-and-Mocks-for-Delphi-with-mORMot">mocking abilities of
<em>mORMot</em></a> will help you in this delicate task - see <a href="http://www.infoq.com/articles/Utilizing-Logging">for instance this web
article</a>.</p>
<p>Do not forget that <em>Synopse</em>, as a company, is <a href="http://synopse.info/forum/viewtopic.php?id=997">able to offer dedicated audit
and support</a> for such a migration.<br />
The sooner, the better.</p>