Slide 1

Slide 1 text

Michał Kulczycki | Senior Software Developer | [email protected] | 2018-11-29

Slide 2

Slide 2 text

Agenda Things I hope to cover: ● Vanessa: Coolblue’s ERP ● Interfaces of DB components ● Virtual interface as a generic implementation ● Examples and real code ● Tips and tricks (and warnings)

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

No easy wins ● Millions of lines of code ● Developed over last 15 years ● Roughly 2000 functions ● 90% with their own data-modules ● Each containing 2 - 70 DB components ● No unit tests

Slide 5

Slide 5 text

Data modules: state 1. Client datasets and lookups (for DB-aware controls), 2. Various utility procedures and view properties, 3. Lots of inherited functionality for the view VIEW DB DATAMODULE

Slide 6

Slide 6 text

Data modules: DB access 1. Customised ADO queries, commands, stored procedures etc., 2. Using common objects shared by hundreds of DMs, 3. Including connection object from main form’s DM (globals, yay) VIEW DB DATAMODULE

Slide 7

Slide 7 text

Data modules: business logic 1. Usually tied to state (e.g. selected rows) 2. Resulting in direct DB access 3. Mixed with APIs, some procedural code and other DMs VIEW DB DATAMODULE

Slide 8

Slide 8 text

Logic: stateless & tested use-cases DB LOGIC API VIEW DATAMODULE

Slide 9

Slide 9 text

Logic: further abstractions injected DB LOGIC API SERVICE 2 SERVICE 3 SERVICE 1 API DB

Slide 10

Slide 10 text

Logic: free of implementation details DB LOGIC API VIEW DATAMODULE

Slide 11

Slide 11 text

Solution: a humble wishlist ● Eventually replaceable by APIs and domain objects ● Zero DB types exposed (incl. TDataset & TField) ● SQL moved from DFMs to … somewhere (and kept static) ● Supporting all types of DB objects and DDL/DML operations ● Independent of client/driver (e.g. ODAC instead of ADO) ● Making logic fully unit-testable (thanks to total abstraction) We wanted something:

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

SQL statement: INSERT instead of: given interface: we can just:

Slide 14

Slide 14 text

SQL statement: DELETE instead of: given interface: we can just:

Slide 15

Slide 15 text

SQL statement: a scalar SELECT instead of: given interface: we can just:

Slide 16

Slide 16 text

SQL statement: a complex SELECT instead of: given type and interface: we can just:

Slide 17

Slide 17 text

SQL statement: a complex, multirow SELECT instead of: given type and interface: we can just:

Slide 18

Slide 18 text

SQL: in DFMs

Slide 19

Slide 19 text

SQL: in external files or resources

Slide 20

Slide 20 text

One small problem Implementation of some specific interface is easy. Multiple implementations also pose no problem. But all boils down to something like: An interface requires some implementation. And we did not want to write thousands of those...

Slide 21

Slide 21 text

(of multiple, unknown interfaces in one class)

Slide 22

Slide 22 text

No content

Slide 23

Slide 23 text

Virtual interfaces are a thing! Virtual interface is: ● an actual Delphi library class called TVirtualInterface ● descending from TInterfacedObject (ref-counted) ● living somewhat forgotten life in System.Rtti

Slide 24

Slide 24 text

Virtual interface’s expectations TVirtualInterface instance is created with two unusual parameters: 1. RTTI’s type information (PTypeInfo) about some interface, 2. OnInvoke event handler it will call when invoked: .. and that is pretty much it!

Slide 25

Slide 25 text

Virtual interface’s birth It’s whole purpose in life is to do two things only: 1. Pretend that it actually is an implementation of that interface, 2. While transparently forwarding all its method calls to OnInvoke event.

Slide 26

Slide 26 text

How is that useful? ● Class does not have to be declared as an implementation of an interface ● Handler (implementation) has to know how to adjust its behaviour based on: ● RTTI information (type description: names, types, attributes etc.) ● arguments (just like any other method accepting those). So, if we can fit all control information into type declaration and normal arguments, we are going to get ourselves a generic implementation of some kind of interfaces. Like any and all SQL statements.

Slide 27

Slide 27 text

Let’s see that in action

Slide 28

Slide 28 text

What about APIs? ● Initially generated from our OpenAPI Delphi code generator ● Generated code is quite simplistic and rigid ● But: implementation is repeatable ● Most complex DTOs can be easily marshalled into JSON ● Special cases can be handled with attributes. SERVICE 2 API DB

Slide 29

Slide 29 text

Let’s see some examples

Slide 30

Slide 30 text

No content

Slide 31

Slide 31 text

RTTI must be requested RTTI information is crucial but Delphi won’t generate it unless your interface: 1. inherits from IInvokable: 2. or is preceded with {$M+}: Fun fact: First one works 99.9% of the time. Until it doesn't.

Slide 32

Slide 32 text

It walks like a duck, but isn’t one ● Virtual interface instance acts like an interface. Runtime. ● You can cast an instance, but you need one first. ● Delphi (and Spring container) will rebel against it:

Slide 33

Slide 33 text

They’ll never know it was a duck

Slide 34

Slide 34 text

Careful with generics ● Generic bloat has been reduced but still is significant. ● Sizeable implementations will bloat your binaries (biggest Vanessa DCU: registration of 1 API + 8 statements), ● Generics are useless during type inspection: you’ll do TypeInfo(T) and proceed with PTypeInfo anyway ● Use generic stub to: 1) Do that cast and, 2) Pass resulting PTypeInfo to an actual implementation.

Slide 35

Slide 35 text

Separate type inspection from execution RTTI data inspection during an Invoke is wrong on many levels: ● Will be executed multiple time (RTTI is fast enough, but still…), ● Often will be too late: context can depend on previous call, ● It is a bit too late to learn that something is missing or wrong, Instead: ● Inspect type once, preferably before instantiation, ● Raise inspection errors, log warnings and information, ● Cache results in some structure and just serve it, ● Every new instance will be fully operational instantly, ● Bonus: they won’t need generics and maybe even PTypeInfo.

Slide 36

Slide 36 text

Self comes first ● Args[0] is not first argument of the Method invoke (It will be in Args[1] if there were any (of course) ● Args[0] is always equal to Self ( in TValue) ● Failure to rember that will result in craziest of run-time errors

Slide 37

Slide 37 text

Michał Kulczycki | Senior Software Developer | [email protected] | 2018-11-29