Write blog posts, guides, tutorials, and long-form content. Sounds like a real person, not AI. Use when the user wants polished written content.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Creates RPC-style endpoint following layered architecture (Controller → Manager → Repository). Use when creating new API endpoints or CRUD operations.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.
Creates complete RPC-style API endpoints following strict layered architecture patterns.
Location: app/module-client/src/main/kotlin/com/yourcompany/client/response/{domain}/
package com.yourcompany.client.response.{domain}
import com.yourcompany.postgresql.entity.{Domain}Entity
import java.time.Instant
import java.util.UUID
data class {Domain}Response(
val id: UUID,
val name: String,
val status: String,
val createdAt: Instant,
val updatedAt: Instant?
) {
companion object {
fun from(entity: {Domain}Entity): {Domain}Response = {Domain}Response(
id = entity.id,
name = entity.name,
status = entity.status,
createdAt = entity.createdAt,
updatedAt = entity.updatedAt
)
}
}
data class {Domain}ListResponse(
val items: List<{Domain}Response>,
val total: Int,
val page: Int,
val limit: Int,
val hasMore: Boolean
)
Location: app/module-client/src/main/kotlin/com/yourcompany/client/request/{domain}/
package com.yourcompany.client.request.{domain}
data class Create{Domain}Request(
val name: String,
val description: String? = null
)
data class Update{Domain}Request(
val name: String? = null,
val description: String? = null,
val status: String? = null
)
Key: All models in module-client, never in controllers or managers.
Location: app/api-application/src/main/kotlin/com/yourcompany/controller/{Domain}Controller.kt
package com.yourcompany.controller
import com.yourcompany.{domain}.contract.{Domain}Manager
import com.yourcompany.client.request.{domain}.Create{Domain}Request
import com.yourcompany.client.request.{domain}.Update{Domain}Request
import com.yourcompany.client.response.{domain}.{Domain}Response
import com.yourcompany.client.response.{domain}.{Domain}ListResponse
import com.yourcompany.exception.throwOrValue
import io.micronaut.http.annotation.*
import io.micronaut.scheduling.TaskExecutors
import io.micronaut.scheduling.annotation.ExecuteOn
import io.micronaut.security.annotation.Secured
import io.micronaut.validation.Validated
import io.swagger.v3.oas.annotations.tags.Tag
import jakarta.validation.Valid
import java.util.UUID
@ExecuteOn(TaskExecutors.IO)
@Validated
@Controller("/api/v1/{domain}")
@Tag(name = "{Domain}", description = "{Domain} API")
@Secured(SecurityRule.IS_AUTHENTICATED)
class {Domain}Controller(
private val {domain}Manager: {Domain}Manager
) {
@Get("/{domain}s")
suspend fun list(
@QueryValue page: Int?,
@QueryValue limit: Int?,
@QueryValue status: String?
): {Domain}ListResponse {
return {domain}Manager.list(
page = page ?: 1,
limit = limit ?: 20,
status = status
).throwOrValue()
}
@Get("/{domain}")
suspend fun getById(
@QueryValue id: UUID
): {Domain}Response {
return {domain}Manager.byId(id).throwOrValue()
}
@Post("/{domain}")
suspend fun create(
@Valid @Body request: Create{Domain}Request
): {Domain}Response {
return {domain}Manager.create(request).throwOrValue()
}
@Post("/{domain}/update")
suspend fun update(
@QueryValue id: UUID,
@Valid @Body request: Update{Domain}Request
): {Domain}Response {
return {domain}Manager.update(id, request).throwOrValue()
}
@Post("/{domain}/delete")
suspend fun delete(
@QueryValue id: UUID
): Boolean {
return {domain}Manager.deleteById(id).throwOrValue()
}
}
Key Points:
@ExecuteOn(TaskExecutors.IO) required for suspend functions@QueryValue id: UUID).throwOrValue() to unwrap EitherLocation: app/module-{domain}/module-api/src/main/kotlin/com/yourcompany/{domain}/contract/{Domain}Manager.kt
package com.yourcompany.{domain}.contract
import arrow.core.Either
import com.yourcompany.client.request.{domain}.Create{Domain}Request
import com.yourcompany.client.request.{domain}.Update{Domain}Request
import com.yourcompany.client.response.{domain}.{Domain}Response
import com.yourcompany.client.response.{domain}.{Domain}ListResponse
import com.yourcompany.exception.ClientException
import java.util.UUID
interface {Domain}Manager {
suspend fun list(
page: Int,
limit: Int,
status: String?
): Either<ClientException, {Domain}ListResponse>
suspend fun byId(id: UUID): Either<ClientException, {Domain}Response>
suspend fun create(
request: Create{Domain}Request
): Either<ClientException, {Domain}Response>
suspend fun update(
id: UUID,
request: Update{Domain}Request
): Either<ClientException, {Domain}Response>
suspend fun deleteById(id: UUID): Either<ClientException, Boolean>
}
Location: app/module-{domain}/module-impl/src/main/kotlin/com/yourcompany/{domain}/impl/Default{Domain}Manager.kt
package com.yourcompany.{domain}.impl
import arrow.core.Either
import arrow.core.left
import arrow.core.right
import com.yourcompany.database.DatabaseContext
import com.yourcompany.{domain}.contract.{Domain}Manager
import com.yourcompany.client.request.{domain}.Create{Domain}Request
import com.yourcompany.client.request.{domain}.Update{Domain}Request
import com.yourcompany.client.response.{domain}.{Domain}Response
import com.yourcompany.client.response.{domain}.{Domain}ListResponse
import com.yourcompany.exception.ClientError
import com.yourcompany.exception.ClientException
import com.yourcompany.postgresql.entity.{Domain}Entity
import com.yourcompany.postgresql.repository.{Domain}Repository
import org.jetbrains.exposed.sql.transactions.transaction
import java.util.UUID
class Default{Domain}Manager(
private val {domain}Repository: {Domain}Repository,
private val db: DatabaseContext
) : {Domain}Manager {
override suspend fun byId(id: UUID): Either<ClientException, {Domain}Response> {
val entity = {domain}Repository.byId(id)
?: return ClientError.{DOMAIN}_NOT_FOUND.asException().left()
return {Domain}Response.from(entity).right()
}
override suspend fun create(
request: Create{Domain}Request
): Either<ClientException, {Domain}Response> {
val entity = {Domain}Entity(
name = request.name,
description = request.description
)
val inserted = transaction(db.primary) {
{domain}Repository.insert(entity)
}
return {Domain}Response.from(inserted).right()
}
override suspend fun update(
id: UUID,
request: Update{Domain}Request
): Either<ClientException, {Domain}Response> {
val existing = {domain}Repository.byId(id)
?: return ClientError.{DOMAIN}_NOT_FOUND.asException().left()
val updated = transaction(db.primary) {
{domain}Repository.update(
id = id,
name = request.name,
description = request.description,
status = request.status
)
} ?: return ClientError.{DOMAIN}_NOT_FOUND.asException().left()
return {Domain}Response.from(updated).right()
}
override suspend fun deleteById(id: UUID): Either<ClientException, Boolean> {
val deleted = transaction(db.primary) {
{domain}Repository.deleteById(id)
}
return if (deleted != null) {
true.right()
} else {
ClientError.{DOMAIN}_NOT_FOUND.asException().left()
}
}
}
Key Points:
{Domain}Response.from(entity) for conversions (companion object pattern)transaction(db.primary) for writesEither.left() for errors, Either.right() for success!! operatorsLocation: app/module-{domain}/module-impl/src/main/kotlin/com/yourcompany/runtime/factory/{Domain}ManagerFactory.kt
package com.yourcompany.runtime.factory
import com.yourcompany.database.DatabaseContext
import com.yourcompany.{domain}.impl.Default{Domain}Manager
import com.yourcompany.{domain}.contract.{Domain}Manager
import com.yourcompany.postgresql.repository.{Domain}Repository
import io.micronaut.context.annotation.Factory
import jakarta.inject.Singleton
@Factory
class {Domain}ManagerFactory {
@Singleton
fun provide{Domain}Manager(
{domain}Repository: {Domain}Repository,
db: DatabaseContext
): {Domain}Manager {
return Default{Domain}Manager({domain}Repository, db)
}
}
Location: app/module-client/src/main/kotlin/com/yourcompany/client/{Domain}Client.kt
package com.yourcompany.client
import com.yourcompany.client.request.{domain}.Create{Domain}Request
import com.yourcompany.client.request.{domain}.Update{Domain}Request
import com.yourcompany.client.response.{domain}.{Domain}Response
import com.yourcompany.client.response.{domain}.{Domain}ListResponse
import retrofit2.http.*
import java.util.UUID
interface {Domain}Client {
@GET("/api/v1/{domain}s")
suspend fun list(
@Header("Authorization") authorization: String,
@Query("page") page: Int? = null,
@Query("limit") limit: Int? = null,
@Query("status") status: String? = null
): {Domain}ListResponse
@GET("/api/v1/{domain}")
suspend fun getById(
@Header("Authorization") authorization: String,
@Query("id") id: UUID
): {Domain}Response
@POST("/api/v1/{domain}")
suspend fun create(
@Header("Authorization") authorization: String,
@Body request: Create{Domain}Request
): {Domain}Response
@POST("/api/v1/{domain}/update")
suspend fun update(
@Header("Authorization") authorization: String,
@Query("id") id: UUID,
@Body request: Update{Domain}Request
): {Domain}Response
@POST("/api/v1/{domain}/delete")
suspend fun delete(
@Header("Authorization") authorization: String,
@Query("id") id: UUID
): Boolean
}
Location: app/api-application/src/test/kotlin/com/yourcompany/{Domain}ControllerTest.kt
See
testing-patterns.mdfor complete test examples.
./gradlew :app:api-application:test --tests "{Domain}ControllerTest"
./gradlew test
This project uses RPC-style endpoints: @Get for reads, @Post for all mutations, query parameters for all IDs:
GET /api/v1/employees # List employees (plural)
GET /api/v1/employee # Get one employee (?id=xxx)
POST /api/v1/employee # Create employee
POST /api/v1/employee/update # Update employee
POST /api/v1/employee/delete # Delete employee (soft)
Rules from API_RULES.md:
/{id})@QueryValue id: UUID/delete, /restore)HTTP Request → Controller → Manager → Repository → Database
Controller: Thin (just delegation), HTTP annotations, @ExecuteOn(TaskExecutors.IO), @Secured, unwrap Either with .throwOrValue()
Manager: All business logic, returns Either<ClientException, T>, wraps DB operations in transactions, never throws exceptions
Repository: Data access only (already exists)
val employee = employeeRepository.byId(id)
?: return ClientError.EMPLOYEE_NOT_FOUND.asException().left()
val entity = repository.byId(id)
?: return ClientError.{DOMAIN}_NOT_FOUND.asException().left()
val existing = repository.byEmail(email)
if (existing != null) {
return ClientError.EMAIL_ALREADY_IN_USE.asException().left()
}
@ExecuteOn(TaskExecutors.IO)@QueryValue for all IDs)!! operatorcompanion object { fun from() }아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.
로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.
아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.
Write blog posts, guides, tutorials, and long-form content. Sounds like a real person, not AI. Use when the user wants polished written content.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Design RPC-style APIs with layered architecture (Controller → Manager → Repository). Use when creating new API endpoints, designing API contracts, or reviewing API patterns.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Run a structured brainstorm session for startup ideas. Takes a theme or problem and generates ideas with quick gut-checks. Use when the user wants to explore a space or generate new ideas.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Run real browser QA with Playwright. Use when testing a frontend feature, verifying UI before PR, smoke testing after deploy, or investigating reported visual bugs.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
CI/CD pipeline patterns for GitHub Actions, PR automation, and deployment workflows. Use when setting up CI, fixing broken pipelines, automating PR checks, or configuring deployment.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Deep competitor analysis. Tear apart a specific competitor or compare multiple competitors. Use when the user names a competitor or asks "who else is doing this?"
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.