API Clients
Given that modules should not have direct dependencies on other modules and given that we're building a micro / macro service based architecture we need a mechanism to communicate between modules when the need arises.
There are two approaches we use in general, depending on whether you are reading or writing data.
Writing Data (Event Sourcing)
If you need to write data to an internal service this should generally be done via events using the eventually consistent paradigm rather that via API calls which can be hard to orchestrate when failures occur.
For more information on how we raise and handle events please the events documentation here
Reading Data (API Clients)
If you need to read data from a service but not change it you should use the API clients to make API calls to retrieve the data you need. To facilitate this we are auto generating API clients that use axois to trigger API calls between our services
API Clients are components and live in the clients folder in the root directory.
Any module that needs to communicate with other modules should reference the modules client rather than the module itself.
We are also generating the types exposed by each modules API so we have a strongly typed client for each module.
Each auto generated API client exposes a set of classes which expose the methods to trigger each path defined in the OpenAPI schema. There is an ApiClientFactory class (components/common/clients/api-client-factory.ts) which manages creating service instances and configuring them.
Use a client as follows
// import InventoryService and its OpenAPI config from the client component
import { RecordDetail, InventoryService } from '@hectare/platform.clients.inventory'
// Get an instance of the InventoryService using the factory on Context
const client = await context.apiClientFactory.create(InventoryService, context.event)
// Call the recordGetByIds() operation
const records = await client.recordGetByIds(payload.map(p => Trade.friendlyId(p.recordId)).join(','))
Test API Client
We use the TestApiClient to trigger API handlers without needing an HTTP service running, this allows us to make inter-service API calls from integration tests, without this these tests would always fail as the APIs will not be running on the CI server for example.
see tests/utils/api-client-factory.ts for more details
Authentication
We need a mechanism to authenticate API calls between services, the intention currently is to keep things simple and access internal services via the internet rather than via the AWS VPC.
Therefore each API call will need to be authenticated with a valid token.
Approach TBC
Auto Generation
There are scripts to run to auto generate the API clients and their associated OpenAPI documents.
pnpm run gen:schemas // regenerate OpenAPI documents and store them in clients/[module]/openapi.json
pnpm run gen:clients // auto generate a client for each module with the types and store here clients/[module]
The schema generation script stores its output in the OpenAPI/auto folder in each module, so when we generate the schemas for inventory we end up with the typescript types for the schemas in the following folder
modules/inventory/openapi/auto
Files in these directories should never be manually edited.
I have also added a shell script to run a few commands which should be executed before each commit, we always need to regenerate schemas and clients, then build the codebase and run the tests which is what this script does
./scripts/pre-commit.sh
There is a shortcut in package.json pnpm run pc