article-writing
FreeWrite blog posts, guides, tutorials, and long-form content. Sounds like a real person, not AI. Use when the user wants polished written content.
The English summary is being prepared. Showing the original description.
Creates RPC-style endpoint following layered architecture (Controller → Manager → Repository). Use when creating new API endpoints or CRUD operations.
The English summary is being prepared. Showing the original description.
Review the instructions the agent will receive before you install.
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() }No reviews yet. Tell others what you thought.
No login needed. Share how it worked for you, ask a question, or note where it did not run.
No comments yet. Be the first to write one.
Write blog posts, guides, tutorials, and long-form content. Sounds like a real person, not AI. Use when the user wants polished written content.
The English summary is being prepared. Showing the original description.
Design RPC-style APIs with layered architecture (Controller → Manager → Repository). Use when creating new API endpoints, designing API contracts, or reviewing API patterns.
The English summary is being prepared. Showing the original description.
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.
The English summary is being prepared. Showing the original description.
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.
The English summary is being prepared. Showing the original description.
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.
The English summary is being prepared. Showing the original description.
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?"
The English summary is being prepared. Showing the original description.