In this guide (18 items)
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-serviceplaceholders with your project’s actual package and service name when applying patterns.
Companion files:
- claude-code-guide.md — index + required reading order.
- ccg-domain-and-ports.md — Steps 1-3 (domain, ports, use case).
- ccg-adapters-and-wiring.md — Step 4 (adapters) + Step 5 (wiring).
- ccg-bugfix-and-testing.md — bug-fix workflow + test patterns.
🔍 Common Tasks
Add a New Domain Entity
- Create entity in
domain/ - Create enum for status/type if needed
- Add business behavior methods
- No annotations, pure Kotlin
Add a New Repository Method
- Add method signature to repository interface in
application/port/outbound/repository/ - Implement in
infrastructure/driven-adapters/jdbc-repository/ - Add SQL query if needed (in Spring Data JDBC repository or custom query)
- Update mapper if needed
Add Cache Layer
- Implement
ChainablePort<T>interface - Use
@Order(50)for cache priority - Implement repository interface
- Check cache first, delegate on miss
- Invalidate/update cache on write operations
Add Metrics
- Create metrics adapter implementing repository interface and
ChainablePort<T> - Use
@Order(75)for metrics priority - Record timers and counters
- Delegate to next in chain
Add Background Job
- Create scheduler class in
infrastructure/entry-points/scheduler-task/ - Use
@Scheduledannotation - Call use case port
- Use
@SchedulerLockfor distributed locking - Handle exceptions and log errors
❌ Common Mistakes to Avoid
1. Spring Annotations in Application Layer
// ❌ WRONG@Service // No Spring in application!class CreateInvoiceUseCase { }
// ✅ CORRECTclass CreateInvoiceUseCase { } // No annotations2. Direct DB Calls in Use Case
// ❌ WRONGclass CreateInvoiceUseCase( private val jdbcTemplate: JdbcTemplate // Direct infrastructure dependency) { }
// ✅ CORRECTclass CreateInvoiceUseCase( private val repository: InvoiceRepositoryPort // Port (interface)) { }3. Business Logic in Adapters
// ❌ WRONG - business logic in adapterclass 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 domainclass 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 writetransaction.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 failuretry { 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@Transactionalin 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 anyXxxPublisherPort - 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
UseCaseConfigbean 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:
- Read
Organization(domain model) - Read
CreateOrganizationUseCase(use case example) - Read
OrganizationRepositoryAdapter(adapter example) - Read
OrganizationController(entry point example) - Read
OutboxScheduler(background job example)
To add a feature:
- Start with domain model
- Define ports in application
- Implement use case
- Implement adapters
- Wire in config
- Test
To fix a bug:
- Identify layer where bug occurs
- Write failing test
- Fix bug
- Verify all tests pass
Remember: When in doubt, read the hexagonal-architecture.md file and follow existing patterns. Consistency is key!