-
Notifications
You must be signed in to change notification settings - Fork 0
Coding Standards
Puneethkumar CK edited this page Mar 17, 2026
·
1 revision
| Convention | Example |
|---|---|
| Base package | com.stablecoin.payments.<service> |
| Layer packages |
application, domain, infrastructure
|
No web/ package |
Controllers under application.controller
|
| 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 |
- Standard camelCase throughout
- Test methods:
should*prefix —shouldActivateMerchant() - Domain state methods: verb phrases —
activate(),suspend(),lock() - Static factory methods:
Merchant.createNew(...),Money.of(...)
- 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)
-
All production code uses constructor injection via
@RequiredArgsConstructor+finalfields -
No field
@Autowiredin production code — only in test base classes - No setter injection
| 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 |
- No wildcard imports — every import is explicit
- Static imports for readability (BDDMockito, AssertJ, fixtures)
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;
}
}| 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 |
- All domain exceptions extend
RuntimeException(unchecked) - Static factory methods:
MerchantNotFoundException.withId(merchantId) - Never expose 5xx internals — sanitize to generic message
{
"code": "MERCH-001",
"status": "Bad Request",
"message": "Merchant with ID xyz not found",
"timestamp": "2026-03-17T10:30:00Z"
}| 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-
|
-
Lombok
@Slf4jon 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) |
- Use
@ConfigurationPropertieson Java records with@Validated -
@ConditionalOnPropertyfor feature toggles (not@Profile) - All
@Profileannotations replaced with@ConditionalOnProperty
@Validated
@ConfigurationProperties(prefix = "app.security")
public record SecurityProperties(
@NotNull Boolean enabled,
@NotBlank String jwksUri
) {}See Database Conventions for full details.
Key rules:
- PostgreSQL-native types:
UUID DEFAULT gen_random_uuid(),TIMESTAMPTZ,JSONB -
VARCHAR(n)notCHAR(n)(Hibernate maps String as VARCHAR) -
@JdbcTypeCode(SqlTypes.NAMED_ENUM)for PostgreSQL enums - Flyway migrations: forward-only,
IF EXISTSguards for roles
- Project Structure — Module and package layout
- Testing Standards — Test-specific conventions
- Design Patterns — Pattern catalog
- Architecture Decision Records — Why these decisions
StableBridge Platform | Source Code | CI/CD | Built with Java 25 + Spring Boot 4 + Temporal + Kafka + Base L2
StableBridge Platform
Architecture & Design
Development
- Getting Started
- Project Structure
- Coding Standards
- Testing Standards
- Database Conventions
- Event Driven Architecture
Operations & Security
Project
Reference