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.
.claude/skills/kunanonj-backend-api-design/SKILL.md| 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.| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 11,783 | 6,234 | -47% | 1 | 1 | 0% | 2,772 | 2,173 | -22% | 0 | 0 | — |
case-02 | fail→pass | 14,972 | 9,052 | -40% | 1 | 1 | 0% | 3,089 | 2,890 | -6% | 0 | 0 | — |
case-03 | fail→pass | 11,339 | 7,370 | -35% | 1 | 1 | 0% | 2,409 | 2,340 | -3% | 0 | 0 | — |
case-04 | fail→pass | 13,472 | 5,666 | -58% | 1 | 1 | 0% | 2,556 | 2,088 | -18% | 0 | 0 | — |
case-05 | pass→pass | 6,053 | 3,996 | -34% | 1 | 1 | 0% | 1,161 | 1,613 | +39% | 0 | 0 | — |
case-06 | pass→pass | 10,986 | 5,589 | -49% | 1 | 1 | 0% | 2,239 | 2,011 | -10% | 0 | 0 | — |
case-07 | fail→pass | 8,611 | 4,663 | -46% | 1 | 1 | 0% | 1,906 | 1,849 | -3% | 0 | 0 | — |
case-08 | fail→pass | 10,637 | 4,432 | -58% | 1 | 1 | 0% | 2,118 | 1,670 | -21% | 0 | 0 | — |
case-09 | fail→pass | 14,164 | 7,368 | -48% | 1 | 1 | 0% | 2,932 | 2,286 | -22% | 0 | 0 | — |
case-10 | fail→pass | 8,532 | 5,655 | -34% | 1 | 1 | 0% | 1,786 | 1,769 | -1% | 0 | 0 | — |
case-11 | pass→pass | 9,199 | 6,292 | -32% | 1 | 1 | 0% | 1,883 | 2,041 | +8% | 0 | 0 | — |
case-12 | pass→pass | 10,777 | 6,466 | -40% | 1 | 1 | 0% | 2,097 | 2,199 | +5% | 0 | 0 | — |
case-13 | fail→pass | 12,115 | 6,593 | -46% | 1 | 1 | 0% | 2,759 | 2,066 | -25% | 0 | 0 | — |
case-18 | fail→pass | 6,853 | 4,363 | -36% | 1 | 1 | 0% | 1,463 | 1,639 | +12% | 0 | 0 | — |
case-14 | fail→pass | 10,590 | 7,431 | -30% | 1 | 1 | 0% | 2,687 | 2,580 | -4% | 0 | 0 | — |
case-15 | fail→pass | 8,981 | 4,974 | -45% | 1 | 1 | 0% | 1,848 | 1,581 | -14% | 0 | 0 | — |
case-16 | fail→pass | 9,467 | 4,954 | -48% | 1 | 1 | 0% | 2,062 | 1,907 | -8% | 0 | 0 | — |
case-17 | fail→pass | 10,830 | 7,079 | -35% | 1 | 1 | 0% | 2,308 | 2,499 | +8% | 0 | 0 | — |
case-19 | pass→pass | 9,134 | 5,776 | -37% | 1 | 1 | 0% | 1,917 | 2,081 | +9% | 0 | 0 | — |
case-20 | pass→pass | 5,248 | 4,185 | -20% | 1 | 1 | 0% | 1,017 | 1,636 | +61% | 0 | 0 | — |
case-21 | fail→pass | 7,659 | 4,496 | -41% | 1 | 1 | 0% | 1,670 | 1,727 | +3% | 0 | 0 | — |
case-22 | pass→pass | 10,627 | 6,577 | -38% | 1 | 1 | 0% | 2,134 | 2,133 | -0% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +68 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.