framework entrypoints

why

framework-discovered entrypoints are the most coupled code in a system: invoked by the platform, not by tests. thin adapters keep business behavior callable from any context — route, cron job, webhook — without copying logic. otherwise policy locks inside framework scaffolding exercisable only by a live request.

rule

every framework-discovered entrypoint is a thin adapter:

  • establish operation scope
  • validate framework-shaped input
  • translate to a typed command or reference
  • delegate domain behavior
  • return the framework result

folder placement and registration mechanics belong to the platform. repositories, provider clients, business policy, and durable writes belong to their normal technical and domain owners.

entrypoint flow

  1. framework invokes its registered handler, class, or resource
  2. adapter establishes execution context, validates platform input
  3. adapter constructs a typed command or reference
  4. service, domain, or adapter owns the behavior and durable writes
  5. entrypoint returns the framework-shaped result or schedules the next operation

good and bad

bad

// hono route (framework entrypoint)
export const getUserWorkflow = hono.post("/user/:id/workflow", async (c) => {
const id = c.req.param("id");
const user = await db.query.user.findUnique({ where: { id } }); // DB read
if (!user) return c.json({ error: "not found" }, 404);
const workflows = await provider.listWorkflows(user.providerId); // Provider call
const filtered = workflows.filter(
(
w, // Business logic
) => w.status === "active" && w.updatedAt > Date.now() - 7 * 24 * 60 * 60 * 1000,
);
const sorted = filtered.sort((a, b) => b.priority - a.priority); // More logic
return c.json({ workflows: sorted });
});

the route does database access, provider I/O, filtering, sorting, and business policy at once:

  • testing requires mocking database, provider, and framework context
  • a business-rule change ("active" means something different) edits the route
  • same filtering in a cron job or another verb means copied, diverging logic
  • logic locked into Hono, untestable in isolation

good

// hono route (thin adapter)
export const getUserWorkflow = hono.post("/user/:id/workflow", async (c) => {
const id = c.req.param("id");
const command: GetUserActiveWorkflowsCommand = { userId: id };
const result = await getUserActiveWorkflows(command);
if (!result.ok) return c.json({ error: result.error }, 400);
return c.json({ workflows: result.workflows });
});
// pure domain service (testable, framework-agnostic)
async function getUserActiveWorkflows(
command: GetUserActiveWorkflowsCommand,
): Promise<Result<Workflow[]>> {
const user = await userRepository.findById(command.userId);
if (!user) return { ok: false, error: "user_not_found" };
const workflows = await workflowProvider.listWorkflows(user.providerId);
const active = filterActiveWorkflows(workflows);
return { ok: true, workflows: sortByPriority(active) };
}
// pure predicate (testable, reusable in cron/tests)
function filterActiveWorkflows(workflows: Workflow[]): Workflow[] {
const sevenDaysAgo = Date.now() - 7 * 24 * 60 * 60 * 1000;
return workflows.filter((w) => w.status === "active" && w.updatedAt > sevenDaysAgo);
}
  • route: translate request, call service, return response
  • service owns business logic, testable without framework or database
  • predicate pure, reusable in cron jobs, webhooks, other routes
  • same filtering elsewhere: call the service, never copy. business rules live once

verification

  • registered entrypoint contains only platform adaptation and named delegation
  • exercise the actual framework boundary, then inspect the shell for storage access, provider calls, or business policy that should move to its owner

search pages

go to any page