Saltar al contenido
Mis Guidelines
Guías
En esta guía (18 elementos)

Claude Code Guide — Tasks, Anti-Patterns & Checklists

Claude Code Guide — Common Tasks, Anti-Patterns & Checklists

Project-agnostic. Part of the Claude Code Guide suite. Covers: common task recipes (new entity, repository method, cache, metrics, background job), anti-patterns to avoid, pre-merge checklist, dependency-direction quick reference, getting-started flow.

Replace com.example.platform / platform-service placeholders with your project’s actual package and service name when applying patterns.

Companion files:


🔍 Common Tasks

Add a New Domain Entity

  1. Create entity in domain/
  2. Create enum for status/type if needed
  3. Add business behavior methods
  4. No annotations, pure Kotlin

Add a New Repository Method

  1. Add method signature to repository interface in application/port/outbound/repository/
  2. Implement in infrastructure/driven-adapters/jdbc-repository/
  3. Add SQL query if needed (in Spring Data JDBC repository or custom query)
  4. Update mapper if needed

Add Cache Layer

  1. Implement ChainablePort<T> interface
  2. Use @Order(50) for cache priority
  3. Implement repository interface
  4. Check cache first, delegate on miss
  5. Invalidate/update cache on write operations

Add Metrics

  1. Create metrics adapter implementing repository interface and ChainablePort<T>
  2. Use @Order(75) for metrics priority
  3. Record timers and counters
  4. Delegate to next in chain

Add Background Job

  1. Create scheduler class in infrastructure/entry-points/scheduler-task/
  2. Use @Scheduled annotation
  3. Call use case port
  4. Use @SchedulerLock for distributed locking
  5. Handle exceptions and log errors

❌ Common Mistakes to Avoid

1. Spring Annotations in Application Layer

// ❌ WRONG
@Service // No Spring in application!
class CreateInvoiceUseCase { }
// ✅ CORRECT
class CreateInvoiceUseCase { } // No annotations

2. Direct DB Calls in Use Case

// ❌ WRONG
class CreateInvoiceUseCase(
private val jdbcTemplate: JdbcTemplate // Direct infrastructure dependency
) { }
// ✅ CORRECT
class CreateInvoiceUseCase(
private val repository: InvoiceRepositoryPort // Port (interface)
) { }

3. Business Logic in Adapters

// ❌ WRONG - business logic in adapter
class InvoiceRepositoryAdapter {
fun save(invoice: Invoice): Invoice {
if (invoice.amount > 1000) { // Business rule in infrastructure!
throw Exception("Amount too large")
}
return jdbcRepository.save(invoice.toEntity())
}
}
// ✅ CORRECT - business logic in domain
class Invoice {
init {
require(amount.value <= BigDecimal(1000)) { "Amount cannot exceed 1000" }
}
}

4. Publishing without the Outbox

// ❌ WRONG - use case calls broker directly (dual-write; events lost on rollback / broker error)
kafkaTemplate.send(topic, key, payload)
// ❌ WRONG - "publish first, outbox as fallback" callback (still dual-write)
publisher.publishCreated(invoice) { topic, key, payload ->
outboxRepository.enqueue(topic, key, payload)
}
// ✅ CORRECT - outbox-only inside the same TX as the business write
transaction.run {
invoiceRepository.save(invoice)
outboxRepository.save(outboxEventFactory.create(
topic = "platform.invoices",
traceId = command.traceId,
aggregate = invoice.id.toString(),
eventName = "InvoiceCreated",
eventType = EventTypes.EVENT,
payload = InvoiceCreatedPayload.from(invoice),
))
}

The codehuntersio-sdk-event-messaging dispatcher (SendOutboxMessagesUseCase) publishes asynchronously, retries on failure, and stops at FAILED after maxAttempts. The use case never touches the broker. See messaging.md §4.

5. Empty Catch Blocks

// ❌ WRONG - silent failure
try {
invoiceRepository.save(invoice)
} catch (e: Exception) {
// swallowed
}
// ✅ CORRECT - log with context and rethrow (or convert to a domain exception)
try {
invoiceRepository.save(invoice)
} catch (e: DataAccessException) {
log.error("Persist failed: invoiceId={}", invoice.id, e)
throw InvoicePersistenceException(invoice.id, e)
}

📋 Checklist for New Features

Before submitting code, verify:

  • Domain entities have NO framework dependencies
  • Use cases have NO Spring annotations
  • Ports (interfaces) defined in application layer
  • Adapters implement ports in infrastructure layer
  • Mappers exist for all boundaries (REST, JDBC, Kafka)
  • Transactions use TransactionPort<R> port, not @Transactional in use case
  • Event publishing uses outbox-only pattern — use case writes domain + outbox in one TX; SDK dispatcher publishes asynchronously
  • No use case injects MessagePublisherPort, KafkaTemplate, RabbitTemplate, or any XxxPublisherPort
  • Per-service @SchedulerLock(name = "outbox-${spring.application.name}") (verify in SDK config / application.yml)
  • Liquibase migration created for database changes
  • All adapters have logging with context (IDs, entity names)
  • Use cases wired in UseCaseConfig bean configuration
  • REST endpoints have validation on request DTOs
  • Unit tests for use cases
  • Integration tests for adapters if complex logic
  • ArchUnit tests still pass (./gradlew test)
  • Gradle build passes (./gradlew build)

🎓 Quick Reference: Dependency Direction

┌─────────────────┐
│ Domain │ ← No dependencies
└─────────────────┘
↑
┌─────────────────┐
│ Application │ ← Depends on Domain only
│ (Use Cases & │
│ Ports) │
└─────────────────┘
↑
┌─────────────────┐
│ Infrastructure │ ← Depends on Application + Domain
│ (Adapters) │
└─────────────────┘
↑
┌─────────────────┐
│ Bootstrap │ ← Depends on all modules
│ (Spring Config) │
└─────────────────┘

Golden Rule: Dependencies always point inward. Never depend outward!


🚀 Getting Started Quickly

To understand the codebase:

  1. Read Organization (domain model)
  2. Read CreateOrganizationUseCase (use case example)
  3. Read OrganizationRepositoryAdapter (adapter example)
  4. Read OrganizationController (entry point example)
  5. Read OutboxScheduler (background job example)

To add a feature:

  1. Start with domain model
  2. Define ports in application
  3. Implement use case
  4. Implement adapters
  5. Wire in config
  6. Test

To fix a bug:

  1. Identify layer where bug occurs
  2. Write failing test
  3. Fix bug
  4. Verify all tests pass

Remember: When in doubt, read the hexagonal-architecture.md file and follow existing patterns. Consistency is key!