Entrypoints
Intent
A service may need:
- GraphQL for product-facing consumption
- HTTP for internal or operational endpoints
- Future transports without rewriting the use case
The same command or query can be invoked from any entrypoint. Only transport-specific mapping differs.
Flow
Example
The handler is shared. Entrypoints call execute on the module handler from the request container.
// shell/cqrs/queries/presence.query.ts
export class PresenceQuery extends Query<PresenceQuery.Input, PresenceQuery.Output> {
// ...
execute = async (input: PresenceQuery.Input): PresenceQuery.Output => {
return await this.presenceRepository.findByCardId(input.cardId);
};
}GraphQL
const presence = await ctx.container.seasonCard.presence.execute({
cardId: seasonCard.id,
});
return presence;Domain errors thrown inside the handler bubble up and are handled by the GraphQL format-error middleware in @repo/core-server. No per-resolver catch required.
HTTP
await request.container.seasonCard.simulatePresence.execute({
cardId: request.query.cardId,
renewalStatus: request.query.renewalStatus,
rewardStatus: request.query.rewardStatus,
});
return reply.status(200).send({ success: true });Domain errors thrown inside the handler bubble up and are handled by the Fastify error middleware in @repo/core-server as RFC 9457 problem+json responses. No per-route catch required.
When a use case returns client errors (e.g. validation failures), map them in the entrypoint. See Errors.
Design rule
If two entrypoints need different behavior, the difference should usually live in mapping or transport handling, not in duplicated business logic.