Adaptador Restify
O adapter Restify conecta o runtime Restify aos contratos HTTP do Jumentix. Ele fica na borda: recebe request, normaliza entrada, chama use cases e transforma o resultado em response.
Tecnologia integrada
Framework Node.js focado em REST, desenhado para APIs de serviço e tratamento explícito de request/response.
- Modelo de runtime: Processo Node/Bun de longa duração
- Contrato Jumentix: handlers chamam controllers/use cases sem vazar tipos do framework para o domínio.
Quando usar
Use quando: APIs de serviço em que semântica REST explícita e clareza operacional importam mais que tamanho do ecossistema.
Quando evitar
Evite para aplicações server com muita UI ou projetos que esperam o ecossistema de middlewares do Express.
Como iniciar ou compor
Ponto de entrada real:
apps/backend-template/src/interface/HTTP/adapters/restify/restify.tsapps/backend-template/src/interface/HTTP/adapters/start-rest-api.tsquando o adapter usa bootstrap por ambiente
Comandos disponíveis no monorepo:
bun run dev:restify
bun run prod:restifyExemplo completo: Task e Category na borda HTTP
type Category = {
id: string;
name: string;
};
type Task = {
id: string;
title: string;
categoryId: string;
completed: boolean;
};
type CreateTaskRequest = {
title: string;
categoryId: string;
};
type HttpRequest = {
body: unknown;
};
type HttpResponse = {
status: number;
body: unknown;
};
const adapterProfile = {
adapter: 'restify',
framework: 'Restify',
runtime: 'Long-running Node/Bun process',
entrypoint: 'apps/backend-template/src/interface/HTTP/adapters/restify/restify.ts',
developmentCommand: 'bun run dev:restify',
productionCommand: 'bun run prod:restify'
} as const;
class TaskCatalog {
private readonly categories = new Map<string, Category>();
private readonly tasks = new Map<string, Task>();
createCategory(name: string): Category {
const category = { id: crypto.randomUUID(), name };
this.categories.set(category.id, category);
return category;
}
createTask(input: CreateTaskRequest): Task {
if (!this.categories.has(input.categoryId)) {
throw new Error('Category not found');
}
const task = {
id: crypto.randomUUID(),
title: input.title.trim(),
categoryId: input.categoryId,
completed: false
};
this.tasks.set(task.id, task);
return task;
}
listTasksByCategory(categoryId: string): Task[] {
return [...this.tasks.values()].filter((task) => task.categoryId === categoryId);
}
}
const catalog = new TaskCatalog();
const delivery = catalog.createCategory('Delivery');
const finance = catalog.createCategory('Finance');
catalog.createTask({ title: 'Prepare invoice batch', categoryId: finance.id });
export async function createTaskController(request: HttpRequest): Promise<HttpResponse> {
const input = request.body as Partial<CreateTaskRequest>;
if (!input.title || !input.categoryId) {
return { status: 400, body: { error: 'title and categoryId are required' } };
}
try {
const task = catalog.createTask({ title: input.title, categoryId: input.categoryId });
return { status: 201, body: { adapterProfile, task } };
} catch (error) {
return {
status: 404,
body: { error: error instanceof Error ? error.message : 'Unknown error' }
};
}
}
export async function listDeliveryTasksController(): Promise<HttpResponse> {
return {
status: 200,
body: { category: delivery, tasks: catalog.listTasksByCategory(delivery.id) }
};
}Checklist de adoção
- O adapter fica restrito à camada HTTP.
- Controllers recebem dados normalizados e chamam use cases.
TaskeCategorypertencem ao domínio, não ao framework.- Erros são convertidos para responses na borda.