Skip to content

Coding Standards

Puneethkumar CK edited this page Mar 17, 2026 · 1 revision

Coding Standards

1. Naming Conventions

Packages

Convention Example
Base package com.stablecoin.payments.<service>
Layer packages application, domain, infrastructure
No web/ package Controllers under application.controller

Classes

Type Pattern Example
REST controllers *Controller MerchantController
Command handlers *CommandHandler MerchantCommandHandler
Domain services *Service ComplianceCheckService
Domain port (repo) *Repository MerchantRepository
Domain port (other) *Port, *Provider KycVerificationPort
JPA repositories *JpaRepository MerchantJpaRepository
DB adapters *RepositoryAdapter MerchantRepositoryAdapter
HTTP adapters *Adapter OnfidoKybAdapter
MapStruct mappers *Mapper MerchantEntityMapper
Domain aggregate Plain noun Merchant, Payment
JPA entity *Entity MerchantEntity
Value objects Plain noun (record) Money, Corridor
Domain events Descriptive noun MerchantActivatedEvent
Test fixtures *Fixtures MerchantFixtures
Configuration *Config or *Properties SecurityConfig

Methods

  • Standard camelCase throughout
  • Test methods: should* prefix — shouldActivateMerchant()
  • Domain state methods: verb phrases — activate(), suspend(), lock()
  • Static factory methods: Merchant.createNew(...), Money.of(...)

2. Code Style

Records and Immutability

  • Java records for all domain models, value objects, events, commands, DTOs
  • @Builder(toBuilder = true) from Lombok for copy-with-modification
  • Restricted builders on aggregate roots: @Builder(access = PACKAGE)
  • Classes only for JPA entities (mutable state required by Hibernate)

Constructor Injection

  • All production code uses constructor injection via @RequiredArgsConstructor + final fields
  • No field @Autowired in production code — only in test base classes
  • No setter injection

Lombok Usage

Annotation Where
@Builder(toBuilder = true) Records and entities
@RequiredArgsConstructor All @Service, @Component, @RestController
@Slf4j Every production class
@Getter JPA entities
@NoArgsConstructor(access = PRIVATE) Utility/fixture classes

Imports

  • No wildcard imports — every import is explicit
  • Static imports for readability (BDDMockito, AssertJ, fixtures)

3. Layer Rules

Controller → CommandHandler (Direct)

Controllers call domain CommandHandlers directly — NO intermediate application service layer.

// Controller: thin HTTP handler (accept DTO → call CommandHandler → map response)
@RestController
@RequiredArgsConstructor
public class MerchantController {
    private final MerchantCommandHandler commandHandler;
    private final MerchantResponseMapper mapper;

    @PostMapping("/v1/merchants")
    public ResponseEntity<MerchantResponse> apply(@RequestBody ApplyRequest request) {
        var merchant = commandHandler.apply(new ApplyMerchantCommand(request.legalName(), ...));
        return ResponseEntity.status(CREATED).body(mapper.toResponse(merchant));
    }
}

// CommandHandler: domain layer, owns all business logic
@Service
@Transactional
@RequiredArgsConstructor
public class MerchantCommandHandler {
    private final MerchantRepository merchantRepository;  // port interface
    private final EventPublisher<Object> eventPublisher;

    public Merchant apply(ApplyMerchantCommand command) {
        var merchant = Merchant.createNew(command.legalName(), ...);
        merchantRepository.save(merchant);
        eventPublisher.publish(MerchantAppliedEvent.from(merchant));
        return merchant;
    }
}

Responsibility Matrix

Layer Package Responsibilities
PUBLIC_API -api module Request/response DTOs only
APPLICATION application.* REST controllers, config, event listeners — thin DTO mapping
DOMAIN domain.* Business logic, aggregates, ports, state machine, events
INFRASTRUCTURE infrastructure.* DB adapters, HTTP adapters, event publishers, JPA entities

4. Error Handling

Exception Design

  • All domain exceptions extend RuntimeException (unchecked)
  • Static factory methods: MerchantNotFoundException.withId(merchantId)
  • Never expose 5xx internals — sanitize to generic message

Error Response Schema

{
  "code": "MERCH-001",
  "status": "Bad Request",
  "message": "Merchant with ID xyz not found",
  "timestamp": "2026-03-17T10:30:00Z"
}

Rules

Rule Rationale
5xx messages sanitized to status.getReasonPhrase() Security — never leak internals
5xx logged at ERROR with stack trace Debugging
4xx logged at INFO (not error) Normal client errors
Error codes centralized in ErrorCodes Consistency
Per-service error code prefix MERCH-, PAY-, FX-, COMP-

5. Logging

  • Lombok @Slf4j on every class — no manual logger instantiation
  • Parameterized logging with {} placeholders (no string concatenation)
  • No PII in logs — use hashed identifiers for email, names
Level Usage
INFO Inbound requests, major business events, state transitions
DEBUG Intermediate processing, cache hits/misses
WARN Degraded operations, fallback triggered
ERROR External failures, unexpected exceptions (5xx)

6. Configuration

  • Use @ConfigurationProperties on Java records with @Validated
  • @ConditionalOnProperty for feature toggles (not @Profile)
  • All @Profile annotations replaced with @ConditionalOnProperty
@Validated
@ConfigurationProperties(prefix = "app.security")
public record SecurityProperties(
    @NotNull Boolean enabled,
    @NotBlank String jwksUri
) {}

7. Database Conventions

See Database Conventions for full details.

Key rules:

  • PostgreSQL-native types: UUID DEFAULT gen_random_uuid(), TIMESTAMPTZ, JSONB
  • VARCHAR(n) not CHAR(n) (Hibernate maps String as VARCHAR)
  • @JdbcTypeCode(SqlTypes.NAMED_ENUM) for PostgreSQL enums
  • Flyway migrations: forward-only, IF EXISTS guards for roles

Related Pages

Clone this wiki locally