Kotlin: Design de Projetos Kotlin Explicado

Última atualização: 2026-08-26

Todas as 24 lições convergem aqui — Charlie parte da análise de requisitos e desenha o modelo de domínio, arquitetura em camadas, seleção de stack tecnológico e contratos de API do OrderProcessor. Este é o passo um do projeto no mundo real.

1. O que Você Aprenderá


2. A História Real de um Arquiteto

(1) Problema: Projetar Sem Planejamento Garante Retrabalho

A equipe do Charlie uma vez pulou o design e começou a codificar diretamente. Três meses depois, o modelo de domínio não correspondia aos requisitos de negócio — 60% do código teve que ser reescrito. Perda: 3 meses de desenvolvimento + 2 meses de retrabalho.

(2) Abordagem Design-First

TEXT 📖 Somente leitura
Semana 1: Requisitos → Modelo de Domínio → Máquina de Estados
Semana 2: Arquitetura → Stack Tecnológico → Contrato de API
Semana 3: Esquema de Banco de Dados → Fronteiras de Módulos → Plano CI/CD
Semana 4+: Desenvolvimento (com blueprint claro)

Invista 3 semanas em design, reduza retrabalho em 60%. "Desenvolvimento rápido" sem design é o caminho mais lento.


3. Análise de Requisitos e Modelagem de Domínio

(1) Requisitos Centrais

Requisito Descrição Prioridade
Criação de pedido Cliente faz pedido, gera pedido pendente P0
Pagamento do pedido Confirmar pedido após pagamento bem-sucedido P0
Envio do pedido Armazém envia, gera número de rastreio P0
Cancelamento do pedido Cliente/sistema cancela pedido, aciona reembolso P0
Roteamento de alto valor Pedidos > 10.000 USD passam por pipeline VIP P1
Processamento em lote Suportar throughput de 5.000 pedidos/seg P1
Event sourcing Mudanças de status do pedido produzem eventos de domínio P2

(2) Modelo de Domínio

KOTLIN
// Entidades centrais de domínio
data class Order(
    val id: String,
    val total: Double,
    var status: OrderStatus,
    val customerId: String,
    val items: List<OrderItem>,
    val createdAt: String,
    var updatedAt: String?
)

data class OrderItem(val sku: String, val quantity: Int, val unitPrice: Double)

sealed class OrderStatus {
    object Pending : OrderStatus()
    data class Processing(val step: Int) : OrderStatus()
    object Paid : OrderStatus()
    object Shipped : OrderStatus()
    object Delivered : OrderStatus()
    data class Cancelled(val reason: String) : OrderStatus()
}

sealed class OrderEvent {
    data class Created(val orderId: String, val total: Double) : OrderEvent()
    data class PaymentReceived(val orderId: String, val amount: Double) : OrderEvent()
    data class Shipped(val orderId: String, val trackingCode: String) : OrderEvent()
    data class Cancelled(val orderId: String, val reason: String) : OrderEvent()
}

(3) Máquina de Estados do Pedido

100%
stateDiagram-v2
    [*] --> Pending: Criar Pedido
    Pending --> Processing: Iniciar Processamento
    Pending --> Cancelled: Cliente Cancela
    Processing --> Paid: Pagamento Sucesso
    Processing --> Cancelled: Pagamento Falhou
    Paid --> Shipped: Enviar Pedido
    Shipped --> Delivered: Entrega Confirmada
    Delivered --> [*]
    Cancelled --> [*]

4. Arquitetura em Camadas

(1) Arquitetura de Quatro Camadas

100%
flowchart TD
    A[Camada Controller<br/>API REST / Validação de Requisição] --> B[Camada Service<br/>Lógica de Negócio / Máquina de Estados]
    B --> C[Camada Repository<br/>Acesso a Dados / Persistência]
    B --> D[Camada Integration<br/>Chamadas a APIs Externas]
    C --> E[(Banco de Dados)]
    D --> F[Serviço de Pagamento]
    D --> G[Serviço de Estoque]

(2) Responsabilidades por Camada

Camada Responsabilidade Tecnologia Chave
Controller Tratamento de requisições HTTP, validação, resposta Spring WebFlux, suspend
Service Lógica de negócio, máquina de estados, publicação de eventos Coroutines, sealed class
Repository Persistência de dados, consultas R2DBC, JPA
Integration Chamadas a serviços externos Ktor Client, Resilience4j

(3) Estrutura de Módulos do Projeto

TEXT 📖 Somente leitura
order-processor/
├── build.gradle.kts
├── src/main/kotlin/com/order/
│   ├── Application.kt                    # Main Spring Boot
│   ├── controller/
│   │   └── OrderController.kt            # Endpoints REST
│   ├── service/
│   │   ├── OrderService.kt               # Lógica de negócio
│   │   └── OrderStateMachine.kt          # Transições de estado
│   ├── repository/
│   │   ├── OrderRepository.kt            # Acesso a dados
│   │   └── EventRepository.kt            # Event store
│   ├── integration/
│   │   ├── PaymentClient.kt              # API de pagamento
│   │   └── InventoryClient.kt            # API de estoque
│   ├── domain/
│   │   ├── Order.kt                      # Entidade
│   │   ├── OrderStatus.kt                # Enum de status
│   │   └── OrderEvent.kt                 # Eventos de domínio
│   ├── config/
│   │   └── AppConfig.kt                  # Configuração
│   └── exception/
│       └── OrderExceptions.kt            # Exceções customizadas
└── src/test/kotlin/com/order/
    └── ...                                # Testes

5. Seleção de Stack Tecnológico

Domínio Escolha Tecnológica Motivo
Framework Spring Boot 3.2 + WebFlux Ecossistema maduro, suporte a corrotinas
Linguagem Kotlin 1.9 Segurança de nulo, corrotinas, data class
Async kotlinx.coroutines Concorrência estruturada, suspend
Serialização kotlinx.serialization Segurança em tempo de compilação, múltiplos formatos
Banco de dados PostgreSQL + R2DBC Driver reativo, amigável a corrotinas
Cliente HTTP Ktor Client Nativo Kotlin, suporte a corrotinas
Cache Redis + kotlinx.coroutines Cache assíncrono
Testes JUnit 5 + MockK Mocking nativo Kotlin
Monitoramento Micrometer + Prometheus Coleta de métricas

6. Design de API

(1) Endpoints RESTful

KOTLIN
// Endpoints da API
// POST   /api/v1/orders              - Criar pedido
// GET    /api/v1/orders              - Listar pedidos (com paginação)
// GET    /api/v1/orders/{id}         - Buscar pedido por ID
// PATCH  /api/v1/orders/{id}/status  - Atualizar status do pedido
// DELETE /api/v1/orders/{id}         - Cancelar pedido
// GET    /api/v1/orders/{id}/events  - Obter histórico de eventos do pedido

(2) DTOs de Requisição/Resposta

KOTLIN
// Requisição
data class CreateOrderRequest(
    val customerId: String,
    val items: List<CreateOrderItemRequest>,
    val shippingAddress: AddressRequest?
)

data class CreateOrderItemRequest(
    val sku: String,
    val quantity: Int,
    val unitPrice: Double
)

// Resposta
data class OrderResponse(
    val id: String,
    val total: Double,
    val status: String,
    val customerId: String,
    val items: List<OrderItemResponse>,
    val createdAt: String,
    val updatedAt: String?
)

// Erro
data class ErrorResponse(
    val code: String,
    val message: String,
    val details: Map<String, String>? = null
)

(3) Princípios de Design de API

Princípio Prática
RESTful Substantivos de recurso + semântica de métodos HTTP
Versionamento Prefixo /api/v1/
Paginação ?page=0&size=20
Filtragem ?status=CONFIRMED&customer=C001
HATEOAS Incluir links relacionados na resposta (opcional)
Formato de erro ErrorResponse unificado

7. Esquema de Banco de Dados

SQL
CREATE TABLE orders (
    id          VARCHAR(20) PRIMARY KEY,
    customer_id VARCHAR(20) NOT NULL,
    total       DECIMAL(12,2) NOT NULL DEFAULT 0,
    status      VARCHAR(20) NOT NULL DEFAULT 'PENDING',
    created_at  TIMESTAMP NOT NULL DEFAULT NOW(),
    updated_at  TIMESTAMP
);

CREATE TABLE order_items (
    id          SERIAL PRIMARY KEY,
    order_id    VARCHAR(20) NOT NULL REFERENCES orders(id),
    sku         VARCHAR(50) NOT NULL,
    quantity    INT NOT NULL,
    unit_price  DECIMAL(10,2) NOT NULL
);

CREATE TABLE order_events (
    id          SERIAL PRIMARY KEY,
    order_id    VARCHAR(20) NOT NULL,
    event_type  VARCHAR(30) NOT NULL,
    payload     JSONB,
    created_at  TIMESTAMP NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_orders_status ON orders(status);
CREATE INDEX idx_orders_customer ON orders(customer_id);
CREATE INDEX idx_events_order ON order_events(order_id);

8. Exemplo Completo: Documento de Design de Arquitetura do OrderProcessor

KOTLIN
// ============================================
// OrderProcessor - Design de Arquitetura
// Funcionalidade: Blueprint completo de arquitetura
// ============================================

// --- Camada de Domínio ---
data class OrderItem(val sku: String, val qty: Int, val unitPrice: Double) {
    val subtotal: Double get() = qty * unitPrice
}

sealed class OrderStatus { 
    object Pending : OrderStatus()
    object Confirmed : OrderStatus()
    object Shipped : OrderStatus()
    object Delivered : OrderStatus()
    data class Cancelled(val reason: String) : OrderStatus()
}

sealed class OrderEvent {
    data class Created(val orderId: String, val total: Double, val customerId: String) : OrderEvent()
    data class Confirmed(val orderId: String) : OrderEvent()
    data class Shipped(val orderId: String, val trackingCode: String) : OrderEvent()
    data class Delivered(val orderId: String) : OrderEvent()
    data class Cancelled(val orderId: String, val reason: String) : OrderEvent()
}

data class Order(
    val id: String,
    val total: Double,
    var status: OrderStatus,
    val customerId: String,
    val items: List<OrderItem>
)

// --- Máquina de Estados ---
object OrderStateMachine {
    fun transition(current: OrderStatus, event: OrderEvent): OrderStatus = when {
        current is OrderStatus.Pending && event is OrderEvent.Created -> OrderStatus.Pending
        current is OrderStatus.Pending && event is OrderEvent.Confirmed -> OrderStatus.Confirmed
        current is OrderStatus.Confirmed && event is OrderEvent.Shipped -> OrderStatus.Shipped
        current is OrderStatus.Shipped && event is OrderEvent.Delivered -> OrderStatus.Delivered
        current is OrderStatus.Pending && event is OrderEvent.Cancelled -> OrderStatus.Cancelled(event.reason)
        current is OrderStatus.Confirmed && event is OrderEvent.Cancelled -> OrderStatus.Cancelled(event.reason)
        else -> throw IllegalStateException("Invalid transition: $current + $event")
    }
}

// --- Resumo da Arquitetura ---
fun main() {
    println("=== Design de Arquitetura do OrderProcessor ===\n")

    println("Stack Tecnológico:")
    println("  Framework: Spring Boot 3.2 + WebFlux")
    println("  Linguagem:  Kotlin 1.9")
    println("  Async:      kotlinx.coroutines")
    println("  Banco:      PostgreSQL + R2DBC")
    println("  Cache:      Redis")
    println("  HTTP:       Ktor Client")
    println("  Testes:     JUnit 5 + MockK")
    println("  Monitoramento: Micrometer + Prometheus")

    println("\nMáquina de Estados do Pedido:")
    val order = Order("ORD-001", 299.99, OrderStatus.Pending, "CUST-001", emptyList())
    val confirmed = OrderStateMachine.transition(order.status, OrderEvent.Confirmed("ORD-001"))
    println("  Pending + Confirmed = $confirmed")

    val shipped = OrderStateMachine.transition(confirmed, OrderEvent.Shipped("ORD-001", "TRK-ABC"))
    println("  Confirmed + Shipped = $shipped")

    val delivered = OrderStateMachine.transition(shipped, OrderEvent.Delivered("ORD-001"))
    println("  Shipped + Delivered = $delivered")

    println("\nEndpoints da API:")
    println("  POST   /api/v1/orders              - Criar pedido")
    println("  GET    /api/v1/orders              - Listar pedidos")
    println("  GET    /api/v1/orders/{id}         - Buscar pedido")
    println("  PATCH  /api/v1/orders/{id}/status  - Atualizar status")
    println("  DELETE /api/v1/orders/{id}         - Cancelar pedido")
    println("  GET    /api/v1/orders/{id}/events  - Histórico de eventos")
}

Saída:

TEXT 📖 Somente leitura
=== Design de Arquitetura do OrderProcessor ===

Stack Tecnológico:
  Framework: Spring Boot 3.2 + WebFlux
  Linguagem:  Kotlin 1.9
  Async:      kotlinx.coroutines
  Banco:      PostgreSQL + R2DBC
  Cache:      Redis
  HTTP:       Ktor Client
  Testes:     JUnit 5 + MockK
  Monitoramento: Micrometer + Prometheus

Máquina de Estados do Pedido:
  Pending + Confirmed = Confirmed
  Confirmed + Shipped = Shipped
  Shipped + Delivered = Delivered

Endpoints da API:
  POST   /api/v1/orders              - Criar pedido
  GET    /api/v1/orders              - Listar pedidos
  GET    /api/v1/orders/{id}         - Buscar pedido
  PATCH  /api/v1/orders/{id}/status  - Atualizar status
  DELETE /api/v1/orders/{id}         - Cancelar pedido
  GET    /api/v1/orders/{id}/events  - Histórico de eventos

9. Exemplos práticos rápidos

▶ Exemplo: Estrutura de projeto em camadas

TEXT 📖 Somente leitura
src/
├── main/kotlin/com/example/orders/
│   ├── domain/             # business models puros
│   │   ├── Order.kt
│   │   ├── OrderStatus.kt
│   │   └── OrderRepository.kt    (interface)
│   ├── application/        # use cases
│   │   ├── CreateOrderUseCase.kt
│   │   ├── ConfirmOrderUseCase.kt
│   │   └── CancelOrderUseCase.kt
│   ├── infrastructure/     # implementações técnicas
│   │   ├── persistence/
│   │   │   ├── JpaOrderRepository.kt
│   │   │   └── OrderEntity.kt
│   │   └── web/
│   │       └── OrderController.kt
│   └── OrderApplication.kt
└── test/kotlin/             # testes paralelos

Saída:

TEXT 📖 Somente leitura
Estrutura convencional por camadas:
- domain/     : modelos de negócio puros (Order, OrderStatus, OrderRepository)
- application/: use cases (CreateOrder, ConfirmOrder, CancelOrder)
- infrastructure/: adapters técnicos (JPA, web/REST)

▶ Exemplo: Domain models

KOTLIN
// Pure data classes + business invariants
sealed class OrderStatus {
    object Created : OrderStatus()
    data class Confirmed(val confirmedAt: Long) : OrderStatus()
    data class Shipped(val tracking: String) : OrderStatus()
    data class Cancelled(val reason: String) : OrderStatus()
}

data class Order(
    val id: String,
    val customerId: String,
    val total: Double,
    val status: OrderStatus = OrderStatus.Created,
    val createdAt: Long = System.currentTimeMillis()
) {
    init {
        require(total > 0) { "Total deve ser positivo" }
        require(id.startsWith("ORD-")) { "ID inválido" }
    }

    fun isConfirmed(): Boolean = status is OrderStatus.Confirmed

    fun canBeCancelled(): Boolean = status is OrderStatus.Created ||
                                    status is OrderStatus.Confirmed
}

// Repository interface
interface OrderRepository {
    suspend fun findById(id: String): Order?
    suspend fun save(order: Order): Order
    suspend fun list(): List<Order>
}

Saída:

TEXT 📖 Somente leitura
Order(id="ORD-001", customerId="Alice", total=299.99, status=Created)
isConfirmed(): false
canBeCancelled(): true

▶ Exemplo: Application use cases

KOTLIN
class ConfirmOrderUseCase(private val repo: OrderRepository, private val notify: Notifier) {

    suspend operator fun invoke(orderId: String): Result<Order> {
        val order = repo.findById(orderId)
            ?: return Result.failure(IllegalArgumentException("Pedido $orderId não encontrado"))

        if (!order.canBeCancelled()) {
            return Result.failure(IllegalStateException("Pedido não pode ser confirmado"))
        }

        val confirmed = order.copy(
            status = OrderStatus.Confirmed(System.currentTimeMillis())
        )

        val saved = repo.save(confirmed)
        notify.orderConfirmed(saved)

        return Result.success(saved)
    }
}

interface Notifier {
    fun orderConfirmed(order: Order)
}

class ConsoleNotifier : Notifier {
    override fun orderConfirmed(order: Order) {
        println("📧 Notificação: pedido ${order.id} confirmado")
    }
}

Saída:

TEXT 📖 Somente leitura
📧 Notificação: pedido ORD-001 confirmado

▶ Exemplo: Injeção de dependência com Koin

KOTLIN
import org.koin.dsl.*

val appModule = module {
    single<OrderRepository> { JpaOrderRepository() }
    single<Notifier> { ConsoleNotifier() }
    factory { ConfirmOrderUseCase(get(), get()) }
    factory { CreateOrderUseCase(get()) }
    factory { CancelOrderUseCase(get(), get()) }
}

// Inicializar app
fun main() {
    val koin = startKoin {
        modules(appModule)
    }

    val confirmUseCase = koin.koin.get<ConfirmOrderUseCase>()
    runBlocking {
        confirmUseCase.invoke("ORD-001")
    }
}

Saída:

TEXT 📖 Somente leitura
📧 Notificação: pedido ORD-001 confirmado
[Container de DI inicializado; UseCase executado via Koin]

▶ Exemplo: Padrão Result para erros

KOTLIN
sealed class OrderError(message: String) : Exception(message) {
    object NotFound : OrderError("Pedido não encontrado")
    data class InvalidStatus(val current: String, val required: String) :
        OrderError("Status atual '$current' não permite '$required'")
    data class ValidationError(override val message: String) : OrderError(message)
}

class OrderService(private val repo: OrderRepository) {
    suspend fun confirm(id: String): Result<Order> {
        val order = repo.findById(id) ?: return Result.failure(OrderError.NotFound)

        if (order.status !is OrderStatus.Created) {
            return Result.failure(
                OrderError.InvalidStatus(
                    current = order.status.toString(),
                    required = "confirm"
                )
            )
        }

        val confirmed = order.copy(
            status = OrderStatus.Confirmed(System.currentTimeMillis())
        )
        return Result.success(repo.save(confirmed))
    }
}

// Uso
runBlocking {
    val result = orderService.confirm("ORD-001")
    result.fold(
        onSuccess = { println("✅ Confirmado: $it") },
        onFailure = { println("❌ Erro: ${it.message}") }
    )
}

Saída:

TEXT 📖 Somente leitura
✅ Confirmado: Order(id=ORD-001, status=Confirmed(confirmedAt=...))

▶ Exemplo: API design com versionamento

KOTLIN
// Versionamento via URL path
@RestController
@RequestMapping("/api/v1/orders")
class OrderControllerV1(private val service: OrderService) {
    @GetMapping
    suspend fun list(): ResponseEntity<List<OrderDTO>> = ResponseEntity.ok(
        service.list().map { it.toV1DTO() }
    )
}

// V2 com novos campos
@RestController
@RequestMapping("/api/v2/orders")
class OrderControllerV2(private val service: OrderService) {
    @GetMapping
    suspend fun list(): ResponseEntity<List<OrderDTOV2>> = ResponseEntity.ok(
        service.list().map { it.toV2DTO() }
    )
}

data class OrderDTO(
    val id: String,
    val total: Double,
    val status: String
)

data class OrderDTOV2(
    val id: String,
    val total: Double,
    val status: String,
    val createdAt: Long,
    val customerId: String,           // novo
    val events: List<OrderEventDTO>   // novo
)

Saída:

TEXT 📖 Somente leitura
GET /api/v1/orders    -> OrderDTO[ ]        (versão legacy)
GET /api/v2/orders    -> OrderDTOV2[ ]      (com customerId, events)

▶ Exemplo: Testes para o design

KOTLIN
// Testes para o domínio puro
class OrderTest {

    @Test
    fun `total deve ser positivo`() {
        assertThrows<IllegalArgumentException> {
            Order(id = "ORD-001", customerId = "Alice", total = -1.0)
        }
    }

    @Test
    fun `pedido novo pode ser cancelado`() {
        val order = Order("ORD-001", "Alice", 299.99)
        assertTrue(order.canBeCancelled())
    }

    @Test
    fun `pedido enviado não pode ser cancelado`() {
        val order = Order(
            "ORD-001", "Alice", 299.99,
            status = OrderStatus.Shipped("TRK-123")
        )
        assertFalse(order.canBeCancelled())
    }
}

Saída:

TEXT 📖 Somente leitura
Testes unitários cobrindo:
- invariants do construtor
- transições válidas de status
- regras de cancelamento

❓ Perguntas Frequentes

P: Projetar o banco de dados primeiro ou a API primeiro? R: Projetar o modelo de domínio primeiro (abordagem DDD). Então tanto a API quanto o banco de dados são projeções do modelo de domínio. O modelo de domínio é central; API e Schema são periféricos.

P: Qual a diferença entre event sourcing e CRUD? R: CRUD armazena apenas o estado atual. Event sourcing armazena todos os eventos de mudança de estado. Event sourcing suporta auditoria, replay e time-travel, mas tem complexidade maior. Comece com CRUD; adicione eventos quando necessário.

P: R2DBC ou JPA — como escolher? R: Novo projeto + WebFlux + corrotinas → R2DBC (não-bloqueante). Projeto JPA existente ou equipe não familiarizada com reativo → JPA + extensões de corrotinas.

P: Quão pequeno deve ser um microsserviço? R: Um microsserviço por contexto delimitado. OrderProcessor é o contexto de "processamento de pedidos" — não enfie pagamento e estoque nele também.

P: Como lidar com versionamento de API? R: Versionamento por caminho de URL (/api/v1/) é o mais simples e intuitivo. Versionamento por header é mais RESTful mas mais complexo. Versionamento por URL é recomendado.

P: Como garantir que o design de arquitetura é implementável? R: Toda decisão arquitetural deve ter um código POC (prova de conceito). Arquitetura no papel + protótipo executável = design implementável.


📖 Resumo


📝 Exercícios

  1. Iniciante (⭐): Use sealed class para projetar uma máquina de estados para um sistema simples de gerenciamento de tarefas (Todo → InProgress → Done / Cancelled). Dica: sealed class TaskStatus
  2. Intermediário (⭐⭐): Desenvolva um contrato de API RESTful completo com data classes de requisição/resposta para 5 endpoints. Dica: CreateOrderRequest / OrderResponse
  3. Avançado (⭐⭐⭐): Desenvolva um documento completo de arquitetura em camadas para um sistema de e-commerce, incluindo modelo de domínio, máquina de estados, API, esquema de banco de dados e stack tecnológico. Dica: Consulte todas as seções desta lição

← Anterior | Próximo →

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%