Assembling apps
This project and documentation are work in progress
So you've
created a set of entities to model your domain
written operations that describe domain-level actions on those entities
defined a 'context' type that operations can depend on for causing side effects
written adapters and bindings for your apps
Now we're ready to put it all together.
1. Instantiate the adapters
Call the $adapter
functions to create your adapters ready-to-use. This step will reserve any resources used across adapter calls.
For example:
2. Combine the adapters
Remember that a library only takes a single adapter, it's up to us to write and use code that can combine adapters into a single function. Our library exports a mergeAdapters
function for this purpose:
3. Pass the adapters to the library
This is done using the createLibrary function:
4. Bind the instantiated library
In our example code, we're using an Express HTTP binding:
That's it. Your app is ready.
Going the other way: what happens when a request comes in?
This section is purely optional, but will help you reason about how everything fits together
So what steps execute when the app receives a HTTP request? Let's take POST /book
as an example.
An express route handler is invoked
This is defined in our binding layer. It calls castBookInput
to check that the incoming POST body is well-formed, then calls book.add
on the library that was passed in step 4.
The library function is called
Let's have another look at what the library defined here:
So lib.book.add
is wrapAdapter(adapter, Operations.addBook)
- let's remind ourselves what this does:
So it's the function returned above at line 3, which takes the operation's input and any params required by the adapters. It calls the adapters to get a context and wrapped operation, then calls the operation with context and input.
The adapter is prepared
We invoke the function returned by mergeAdapters
earlier and this gives us a combined context.
If re-creating the context on every request becomes too inefficient, we can rewrite the context to be evaluated 'lazily', e.g. using getters to construct parts of the context tree on demand. Generally this won't be a problem though.
Alternatively we can look to move work out from the 'inner' adapter
function into the 'outer' $adapter
function that was called in step 1.
The operation is invoked
The operation function here might itself wrap the original operation function - in the case of a PostgreSQL backend, this is the point where we would begin a database transaction, ensuring that any errors mid-operation cause changes to roll back.
The operation uses the context, entity codecs and error constructors to perform side effects, return data and / or throw an exception.
The operation's result is returned
Which then goes back to the original Express binding function. This decides whether the result was a success (resulting in e.g. HTTP 200), an error (resulting in a different code), and how to format the result data to the user.
Last updated