Loading skill
Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Design RPC-style APIs with layered architecture (Controller → Manager → Repository). Use when creating new API endpoints, designing API contracts, or reviewing API patterns.
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | -22% | 0% |
| case-02 | ✗→✓ | ▲ Improved | -6% | 0% |
| case-03 | ✗→✓ | ▲ Improved | -3% | 0% |
| case-04 | ✗→✓ | ▲ Improved | -18% | 0% |
| case-07 | ✗→✓ | ▲ Improved | -3% | 0% |
GET /api/v1/employees # List (plural)
GET /api/v1/employee # Get one (?id=xxx)
POST /api/v1/employee # Create
POST /api/v1/employee/update # Update (?id=xxx)
POST /api/v1/employee/delete # Soft delete (?id=xxx)
POST /api/v1/employee/restore # Restore (?id=xxx)
POST /api/v1/sync/employees # Action@QueryValue, never @PathVariable/employee not /employees/{id}/employees/delete, /restore, /syncController → thin, just delegates
↓
Manager → business logic, transactions, Either returns
↓
Repository → data access only, no business logic.throwOrValue()Either<ClientException, T>transaction(db.primary) { }db.replica for reads, db.primary for writesdeletedAt.isNull()The core controller delegation pattern:
kotlin@Get("/employee") suspend fun getEmployee(@QueryValue id: UUID): EmployeeResponse { return employeeManager.findById(id).throwOrValue() }
companion object { fun from(entity) } in module-client/response/{domain}/EmployeeListResponse with items, total, page, limit, hasMoreClientError.NOT_FOUND.asException().left() from managers, never throw@Factory class with @Singleton method, wire repos + db into manager> See code-patterns.md for complete controller, response model, pagination, error handling, and factory bean templates.
@QueryValue params MUST have explicit snake_case names. The frontend axios interceptor sends project_id but Micronaut matches the literal param name. Write @QueryValue("project_id") projectId: UUID, not bare @QueryValue projectId: UUID.@Put, @Delete, or @Patch. This is RPC-style — all mutations are @Post. The only @Get is for reads.private val fooRepository: FooRepository in a controller, move it to the manager.andWhere {} not second .where {}. Calling .where {} twice replaces the first condition. Use .andWhere {} to chain.@ExecuteOn(TaskExecutors.IO). Without it, suspend functions may hang or run on the wrong thread pool. Every controller needs it.Other measured skills in the registry, with their headline benchmark lift.