6. v1 API structure
Date: 2021-01-29
Status
Accepted
Context
We have begun implementing a new pattern for API endpoints (dubbed “v1”) to bring a consistent approach, and improve on the “v0” endpoints which are riddled with bespoke code, confusing flow and security concerns.
However, we have never documented this “consistent approach”, instead relying on developers learning and adopting it naturally. As such our v1 API endpoints are liable to fall down the same trap of inconsistency and confusion.
This ADR decision has been reached not through rigorous exploration of best practices, but simply to document the current state so that we can fully convert our endpoints to a consistent style and unlock future improvements.
Decision
API endpoints for v1 controllers must either create a query (Application\Model\Query
namespace) if retrieving data, or a command (Application\Model\Command
namespace) if writing data, and process it with an associated handler. The controller should not have any dependencies, such as services or repositories.
Data must be sent to the API in JSON format.
Data must be returned from the API in JSON format, using the controller’s serialize
method and specifying suitable serialization groups. The data must be returned directly (e.g. {"id": 1, ...}
) rather than in a nested object (e.g. {"data": {"id": 1, ...}}
).
Any errors and exceptions shall be thrown by the query or command handler, and the v1 controller must pass them to ApiProblem
to convert into a standardised error response. There should not be any custom error responses.
Consequences
We acknowledge that once we have adopted this structure we may need to carry out further work to improve the architecture of our APIs. However, adopting a standard structure will make such changes easier, as we will be able to apply them consistently.
We’re also aware that this structure means every command is wrapped in a single database transaction by the https://tactician.thephpleague.com/plugins/doctrine/
, which doesn’t allow us to break up large transactions or return partial successes (erroring on some records but saving others). To avoid these issues, we may need to contradict the v1 rules by calling services directly or generating errors manually.