Data structure with domain nomenclature. It has a parser to obtain parameters from user input, a main computation that can be called, and it's bound to a transport used to exchange parameters and results with the call site.
The execution is always on the server-side.
Actions are always namespaced inside a name that will represent the main domain concept it manipulates.
Type used to model how a given action is called and have its results delivered to the call site. An action that is supposed to be called over HTTP will have the http
transport.
This is important to limit how actions can be called since you might have actions that are not supposed to be exposed over HTTP (background jobs are usually a good example of this case).
A Query is an Action that will have no side-effect on the server side. A Mutation on the other hand will have side-effects (e.g. database update, a call to an external API or writing some file on disk).
Try to limit your Query actions to simple database reads where you can afford to send cached data or constant values. Most of your actions are probably going to be mutations.
Start by scaffolding the necessary files for your actions using the command line interface:
dx-cli add:domain messages
This command will create the messages
folder under domain-logic
and adds the necessary wiring to domain-logic/index.ts
.
You will find in the domain-logic/messages/index.ts
file an empty domain called messages
:
import { exportDomain } from '../prelude'
const messages = exportDomain('messages', {})
export { messages }
Then import some useful function from prelude inside domain-logic/messages/index.ts
so we can build your first action:
import { exportDomain, makeAction } from '../prelude'
After that we can get our action constructor for the http
transport, since our first action will be a simple hello world that takes no parameters over a HTTP GET
call.
You can destructure http action constructor by using the line:
const { query } = makeAction.http
Note that there are always 2 kinds of constructors, query
and mutation
.
The next step is to call the constructor and store a key named after your action inside the messages
domain:
const messages = exportDomain('messages', {
hello: query()(async () => ({ message: 'Hello World' })),
})
Let's assume that our domain here is a simple translation engine. When you call a given action in this domain called messages
that action will give a string that can be used as copy on your user interface. Similar to an I18n
function.
Export at the end of the file the object that you have built with the actions.
The entire file domain-logic/messages/index.ts
should look like this:
import { exportDomain, makeAction } from '../prelude'
const { query } = makeAction.http
const messages = exportDomain('messages', {
hello: query()(async () => ({ message: 'Hello World' })),
})
export { messages }
To call your action from a page on the browser you will code a React component on a Next.JS application that sits on the ui
folder.
dx-cli add:page home
const result = await messages.hello.run()
console.log(result) // Hello world
const result = await messages.hello.run().catch(() => 'Fallback')
console.log(result) // Fallback
More info on why we designed things the way we did can be found in the architecture notes document