# Banking Domain Conventions

> Use when writing code in a Firefly Banking Platform (firefly-oss) service — applies domain organization, service taxonomy, inter-service communication patterns, POM inheritance, and platform-specific conventions

- Skill: `diegosouzapw/banking-domain-conventions` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add diegosouzapw/banking-domain-conventions`
- Raw SKILL.md: https://api.skillmd.com/api/skills/diegosouzapw/banking-domain-conventions/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: diegosouzapw (https://skillmd.com/u/diegosouzapw)
- Updated: 2026-09-08
- Page: https://skillmd.com/skills/diegosouzapw/banking-domain-conventions

---


# Firefly Banking Platform Conventions

## 1. Service Taxonomy

The Firefly Banking Platform organizes ~55 repositories into six domain prefixes. Each prefix defines the service's role, architectural tier, and framework starter.

| Prefix | Role | Tier | Framework Starter | Examples |
|--------|------|------|-------------------|----------|
| `core-banking-*` | Banking primitives: accounts, cards, ledger, payments | Core (CRUD + business rules) | `fireflyframework-starter-core` | `core-banking-accounts`, `core-banking-ledger`, `core-banking-payments`, `core-banking-cards`, `core-banking-psdx` |
| `core-lending-*` | Lending primitives: origination, servicing, credit, collateral | Core (CRUD + business rules) | `fireflyframework-starter-core` | `core-lending-loan-origination`, `core-lending-loan-servicing`, `core-lending-credit-scoring`, `core-lending-collateral-management`, `core-lending-mortgage` |
| `core-common-*` | Cross-cutting capabilities: customers, products, documents, security | Core (CRUD + business rules) | `fireflyframework-starter-core` | `core-common-customer-mgmt`, `core-common-product-mgmt`, `core-common-kycb-mgmt`, `core-common-user-mgmt`, `core-common-security-vault` |
| `core-data-*` | External data integration and templates | Core (data access) | `fireflyframework-starter-core` | `core-data-credit-bureaus`, `core-data-template` |
| `domain-*` | Orchestration of multiple core services into business workflows | Domain (orchestration) | `fireflyframework-starter-domain` | `domain-customer-people`, `domain-customer-kyc-kyb`, `domain-lending-loan-origination`, `domain-product-catalog`, `domain-distributor-branding` |
| `exp-*` | Backend-for-Frontend organized by user journey | Experience (BFF) | `fireflyframework-starter-application` | `exp-onboarding`, `exp-lending`, `exp-profile`, `exp-payments` |
| `app-*` | Entry-point applications: gateways, authenticators, configurators | Application (gateway/edge) | `fireflyframework-starter-application` | `app-authenticator`, `app-configurator-masters` |

Additional non-service repos:

| Repo | Purpose |
|------|---------|
| `firefly-parent` | Platform parent POM (extends `fireflyframework-parent`) |
| `firefly-bom` | Bill of Materials for all `com.firefly` artifacts |
| `firefly-config` | Centralized Spring Cloud Config repository (dev/pre/pro profiles) |

## 2. Package Naming

All Firefly platform code uses `groupId` = `com.firefly`. Java packages follow the pattern:

```
com.firefly.{tier}.{domain-short-name}.{module-layer}.{sub-package}
```

Real examples from the codebase:

| Repo | Module | Package Root |
|------|--------|-------------|
| `core-common-customer-mgmt` | `-interfaces` | `com.firefly.core.customer.interfaces.dtos` / `...interfaces.enums` |
| `core-common-customer-mgmt` | `-models` | `com.firefly.core.customer.models.entities` / `...models.repositories` |
| `core-common-customer-mgmt` | `-core` | `com.firefly.core.customer.core.services` / `...core.mappers` |
| `core-common-customer-mgmt` | `-web` | `com.firefly.core.customer.web.controllers` |
| `core-common-customer-mgmt` | `-sdk` | `com.firefly.core.customer.sdk.api` / `...sdk.model` / `...sdk.invoker` |
| `core-lending-loan-origination` | `-interfaces` | `com.firefly.core.lending.origination.interfaces.dtos` / `...interfaces.enums` |
| `core-lending-loan-origination` | `-core` | `com.firefly.core.lending.origination.core.services` / `...core.mappers` |
| `core-lending-loan-origination` | `-web` | `com.firefly.core.lending.origination.web` |
| `domain-customer-people` | `-core` | `com.firefly.domain.people.core.customer.commands` / `...core.customer.handlers` |
| `domain-customer-people` | `-infra` | `com.firefly.domain.people.infra` |
| `domain-customer-people` | `-web` | `com.firefly.domain.people.web.controller` |
| `domain-customer-people` | `-sdk` | `com.firefly.domain.people.sdk.api` / `...sdk.model` |
| `exp-onboarding` | `-web` | `com.firefly.exp.onboarding.web` / `...web.controllers` |
| `app-authenticator` | `-web` | `com.firefly.app.orchestrator.web` / `...web.controllers` |

Key pattern: the domain short name drops the prefix category and management suffix. For example, `core-common-customer-mgmt` becomes `core.customer`, and `domain-customer-people` becomes `domain.people`.

## 3. POM Inheritance Chain

Every service follows a three-level POM hierarchy:

```
org.fireflyframework:fireflyframework-parent (1.0.0-SNAPSHOT)
    |
    v
com.firefly:firefly-parent (1.0.0-SNAPSHOT)
    |
    v
com.firefly:{service-artifactId} (1.0.0-SNAPSHOT)
```

### What `fireflyframework-parent` provides

- Spring Boot parent (version management for Spring Boot, Spring Cloud, etc.)
- Plugin management (maven-compiler-plugin, spring-boot-maven-plugin, etc.)
- Dependency versions for framework artifacts (`org.fireflyframework` groupId)

### What `firefly-parent` adds on top

- **Imports two BOMs**: `fireflyframework-bom` (version `26.02.06`) and `firefly-bom` (version `1.0.0-SNAPSHOT`)
- **OpenAPI conventions**: Properties `base.package`, `openapi.base.package`, `openapi.model.package`, `openapi.api.package`
- **OpenAPI generation profile** (`generate-openapi`): always active unless `-DskipOpenApiGen`. Uses `spring-boot-maven-plugin` to start/stop a lightweight `OpenApiGenApplication`, then `springdoc-openapi-maven-plugin` to fetch the spec from `http://localhost:18080/v3/api-docs.yaml` and write it to `target/openapi/openapi.yml`
- **Skip-by-default control**: `openapi.gen.skip=true` by default; only `-web` modules set it to `false`

### Service-level POM pattern

```xml
<parent>
    <groupId>com.firefly</groupId>
    <artifactId>firefly-parent</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <relativePath/>
</parent>

<artifactId>core-common-customer-mgmt</artifactId>
<packaging>pom</packaging>
<modules>
    <module>core-common-customer-mgmt-core</module>
    <module>core-common-customer-mgmt-interfaces</module>
    <module>core-common-customer-mgmt-models</module>
    <module>core-common-customer-mgmt-sdk</module>
    <module>core-common-customer-mgmt-web</module>
</modules>

<properties>
    <java.version>25</java.version>
</properties>
```

All services use Java 25 with `maven.compiler.release` set accordingly.

## 4. BOM Usage

`firefly-bom` (`com.firefly:firefly-bom:1.0.0-SNAPSHOT`) is a flat POM that manages versions for every artifact in the platform. It is imported automatically by `firefly-parent`, so individual services never need to import it directly.

**What it manages** (all at `1.0.0-SNAPSHOT`):

- **7 common libraries**: `library-common-core`, `library-common-web`, `library-common-r2dbc`, `library-common-auth`, `library-common-eda`, `library-common-cache`, `library-common-batch`
- **2 domain libraries**: `library-psps`, `library-rails`
- **3 utility libraries**: `library-utils`, `library-commons-validators`, `library-workflow-engine`
- **2 plugin manager libs**: `library-plugin-manager-core`, `library-plugin-manager-api`
- **5 core-banking** service artifacts + **5 matching SDKs**
- **11 core-lending** service artifacts + **11 matching SDKs**
- **15 core-common** service artifacts + **15 matching SDKs**
- **2 core-data** service artifacts + **2 matching SDKs**
- **10 domain** service artifacts + **10 matching SDKs**
- **3 application** artifacts + **1 app SDK** (`bfc-initconfig-sdk`)
- **1 flow SDK**: `firefly-flow-sdk`

**Version strategy**: All artifacts use `1.0.0-SNAPSHOT` during development. The BOM ensures no version conflicts across the platform.

When you add a dependency on another Firefly service's SDK, you omit the `<version>` tag because the BOM manages it:

```xml
<dependency>
    <groupId>com.firefly</groupId>
    <artifactId>core-common-customer-mgmt-sdk</artifactId>
    <!-- version managed by firefly-bom via firefly-parent -->
</dependency>
```

## 5. Module Structure

### Core services (`core-banking-*`, `core-lending-*`, `core-common-*`, `core-data-*`)

| Module | Content | Key Dependencies |
|--------|---------|-----------------|
| `{service}-interfaces` | DTOs, enums, API request/response contracts | `fireflyframework-utils`, `fireflyframework-validators`, `swagger-annotations`, `jackson-databind` |
| `{service}-models` | JPA/R2DBC entities, Spring Data repositories, Flyway migrations | `{service}-interfaces`, `fireflyframework-r2dbc`, `spring-boot-starter-data-r2dbc`, `r2dbc-postgresql`, `flyway-core` |
| `{service}-core` | Service interfaces, service impls, MapStruct mappers | `{service}-interfaces`, `{service}-models`, `fireflyframework-starter-core`, `mapstruct` |
| `{service}-web` | Spring Boot application class, REST controllers, `application.yaml` | `{service}-core`, `{service}-interfaces`, `fireflyframework-web`, `springdoc-openapi-starter-webflux-ui`, `micrometer-registry-prometheus` |
| `{service}-sdk` | Auto-generated API client from OpenAPI spec | `{service}-web` (provided scope), `openapi-generator-maven-plugin`, `okhttp3`, `swagger-annotations` |

### Domain services (`domain-*`)

| Module | Content | Key Dependencies |
|--------|---------|-----------------|
| `{service}-infra` | SDK client factories, `@ConfigurationProperties` for downstream base paths, third-party integration services (real or dummy) | Core service SDKs (e.g., `core-common-customer-mgmt-sdk`), `fireflyframework-utils` |
| `{service}-interfaces` | Domain-level DTOs and contracts | `{service}-core`, `fireflyframework-utils`, `fireflyframework-validators` |
| `{service}-core` | Commands, handlers, sagas/workflows, domain services (CQRS pattern) | `{service}-infra`, core SDKs, `fireflyframework-starter-domain` |
| `{service}-web` | REST controllers, Spring Boot app, `application.yaml` | `{service}-interfaces`, `fireflyframework-web`, `springdoc-openapi-starter-webflux-ui` |
| `{service}-sdk` | Auto-generated API client from OpenAPI spec | Same pattern as core SDKs |

### Experience services (`exp-*`)

| Module | Content | Key Dependencies |
|--------|---------|-----------------|
| `{service}-interfaces` | Journey-specific DTOs and API contracts | `fireflyframework-utils`, `fireflyframework-validators` |
| `{service}-infra` | SDK client factories for domain services | Domain service SDKs (e.g., `domain-customer-people-sdk`), `fireflyframework-utils` |
| `{service}-core` | Journey orchestration, response shaping, channel logic | `{service}-infra`, domain SDKs, `fireflyframework-starter-application` |
| `{service}-web` | REST controllers, Spring Boot app, `application.yaml` | `{service}-interfaces`, `fireflyframework-web`, `springdoc-openapi-starter-webflux-ui` |
| `{service}-sdk` | Auto-generated API client from OpenAPI spec | Same pattern as domain SDKs |

Note: Experience services consume **only** domain SDKs, never core SDKs directly. They have `-infra` for domain SDK client factories.

### Application services (`app-*`)

| Module | Content | Key Dependencies |
|--------|---------|-----------------|
| `{service}-interfaces` | Lightweight DTOs for gateway-level operations | `fireflyframework-utils` |
| `{service}-core` | Orchestration logic, security configuration | `fireflyframework-utils`, `mapstruct` |
| `{service}-web` | Gateway application, security config, controllers | `{service}-interfaces`, `fireflyframework-starter-application`, `spring-cloud-starter-gateway` |

Note: App services typically do **not** have `-models` or `-sdk` modules. They also do not have `-infra`.

## 6. Inter-Service Communication

### Synchronous: SDK Import

Services communicate synchronously by importing another service's `-sdk` module. The SDK is an auto-generated OpenAPI client using `openapi-generator-maven-plugin` with the `webclient` library (reactive).

**Concrete example** -- `domain-customer-people` calling `core-common-customer-mgmt`:

1. The `-infra` module declares the SDK dependency:
   ```xml
   <dependency>
       <groupId>com.firefly</groupId>
       <artifactId>core-common-customer-mgmt-sdk</artifactId>
   </dependency>
   ```

2. A `@ConfigurationProperties` class maps the base URL:
   ```java
   @ConfigurationProperties(prefix = "api-configuration.common-platform.customer-mgmt")
   public class CustomerMgmtProperties {
       private String basePath;
   }
   ```

3. A `ClientFactory` creates API beans:
   ```java
   @Component
   public class ClientFactory {
       private final ApiClient apiClient;

       public ClientFactory(CustomerMgmtProperties props) {
           this.apiClient = new ApiClient();
           this.apiClient.setBasePath(props.getBasePath());
       }

       @Bean
       public PartiesApi partiesApi() {
           return new PartiesApi(apiClient);
       }
       // ... more API beans
   }
   ```

4. `application.yaml` provides the base path:
   ```yaml
   api-configuration:
     common-platform.customer-mgmt:
       base-path: http://localhost:8082
   ```

5. Environment-specific overrides come from `firefly-config`:
   ```yaml
   # config/dev/application-dev.yaml
   endpoints:
     common:
       core-common-customer-mgmt: https://app-core-common-customer-mgmt.dev.soon.es
   ```

### Asynchronous: Events via Kafka

Domain services use the Firefly EDA (Event-Driven Architecture) library for async communication:

```yaml
firefly:
  eda:
    enabled: true
    default-publisher-type: KAFKA
    default-connection-id: default
    publishers:
      kafka:
        default:
          default-topic: domain-layer
          bootstrap-servers: localhost:9092
```

Domain services also enable CQRS and saga support:

```yaml
firefly:
  cqrs:
    enabled: true
    command:
      timeout: 30s
      metrics-enabled: true
      tracing-enabled: true
    query:
      timeout: 15s
      caching-enabled: true
      cache-ttl: 15m
  saga.performance.enabled: true
  stepevents.enabled: true
```

## 7. Layer Rules

```
+-------------------+
|  channel (web/    |  Browser, Mobile App, API Consumer
|  mobile/API)      |
+-------------------+
         |
         v
+-------------------+       +-------------------+
|  exp-* (BFF per   |       |  app-* (edge/     |
|  user journey)    |       |  gateway/auth)    |
+-------------------+       +-------------------+
         |                           |
         | calls via domain SDK      | calls via domain SDK or routes
         v                           v
+-------------------+
|   domain-* (orch) |  Workflows, Sagas, CQRS Commands/Queries
+-------------------+
         |
         | calls via SDK (sync) or Kafka events (async)
         v
+-------------------+
| core-* (CRUD/biz) |  Entities, Repositories, Business Rules
+-------------------+
         |
         | R2DBC / PostgreSQL
         v
+-------------------+
|    Database        |
+-------------------+
```

**Strict dependency direction -- violations break the architecture**:

| Rule | Rationale |
|------|-----------|
| Core NEVER imports a domain SDK | Core services are self-contained data owners |
| Core NEVER imports another core service SDK | Core services do not orchestrate each other |
| Domain orchestrates core services via their SDKs | Domain is the orchestration layer |
| Domain may import other domain SDKs only for cross-domain workflows | Keep coupling minimal |
| Experience (`exp-*`) consumes ONLY domain SDKs | Experience is a BFF layer, never accesses core directly |
| App orchestrates domain services (and occasionally core for infra concerns) | App is the edge entry point |
| Data services read from core but never write back | Data is a read projection |

**The `-infra` module exists ONLY in domain services**. It houses SDK client factories and configuration properties that wire up downstream core service connections. Core services do not have an `-infra` module because they do not call other services.

## 8. Configuration

### Spring Cloud Config Repository (`firefly-config`)

Centralized configuration lives in `firefly-config` with the following structure:

```
firefly-config/
  config/
    dev/
      application.yaml            # Local development defaults
      application-dev.yaml        # Dev environment endpoints for all services
      domain/
        customer-domain-people-dev.yaml
        customer-domain-kyc-kyb-dev.yaml
        lending-domain-loan-origination-dev.yaml
        ...
    pre/
      application-pre.yaml        # Pre-production endpoints
      domain/
        customer-domain-people-pre.yaml
        ...
    pro/
      application-pro.yaml        # Production endpoints
      domain/
        customer-domain-people-pro.yaml
        ...
```

### Environment Profiles

| Profile | Purpose | Endpoint pattern |
|---------|---------|-----------------|
| `dev` | Development | `https://app-{service-name}.dev.soon.es` |
| `pre` | Pre-production / staging | `https://app-{service-name}.pre.soon.es` |
| `pro` | Production | `https://app-{service-name}.pro.soon.es` |

### Service-Level Configuration (`application.yaml`)

Each service's `-web` module contains an `application.yaml` with:

- `spring.application.name` matching the repo name (e.g., `core-common-customer-mgmt`)
- Database connection via environment variables: `DB_HOST`, `DB_PORT`, `DB_NAME`, `DB_SSL_MODE`, `DB_USERNAME`, `DB_PASSWORD`
- R2DBC connection pool settings
- Flyway migrations from `classpath:db/migration`
- SpringDoc OpenAPI scanning the controller package
- Actuator endpoints: `health`, `info`, `prometheus`
- Liveness and readiness probes enabled
- Virtual threads enabled (`spring.threads.virtual.enabled: true`)

Domain services additionally configure `firefly.cqrs`, `firefly.eda`, `firefly.saga`, and `api-configuration` base paths for downstream SDKs.

### Profile-Specific Sections

Services embed multi-profile configuration using Spring's `---` document separator:

```yaml
---
spring:
  config:
    activate:
      on-profile: dev
logging:
  level:
    com.firefly: DEBUG

---
spring:
  config:
    activate:
      on-profile: prod
logging:
  level:
    com.firefly: INFO
springdoc:
  api-docs.enabled: false
  swagger-ui.enabled: false
```

## 9. API Contract Conventions

### OpenAPI Spec Ownership

Each service owns its OpenAPI spec. The spec is auto-generated at build time from the `-web` module's annotated controllers:

1. The `-web` module sets `openapi.gen.skip=false` and `openapi.gen.mainClass` to a lightweight Spring Boot application:
   ```xml
   <properties>
       <openapi.gen.skip>false</openapi.gen.skip>
       <openapi.gen.mainClass>com.firefly.core.customer.web.openapi.OpenApiGenApplication</openapi.gen.mainClass>
   </properties>
   ```

2. During `integration-test` phase, `spring-boot-maven-plugin` starts the app on port 18080, `springdoc-openapi-maven-plugin` fetches the spec from `/v3/api-docs.yaml`, and writes it to `target/openapi/openapi.yml`.

### SDK Generation

The `-sdk` module uses `openapi-generator-maven-plugin` (v7.0.1) to generate a Java client from the spec:

```xml
<configuration>
    <inputSpec>${project.parent.basedir}/${project.parent.artifactId}-web/target/openapi/openapi.yml</inputSpec>
    <generatorName>java</generatorName>
    <library>webclient</library>
    <apiPackage>com.firefly.core.customer.sdk.api</apiPackage>
    <modelPackage>com.firefly.core.customer.sdk.model</modelPackage>
    <invokerPackage>com.firefly.core.customer.sdk.invoker</invokerPackage>
    <configOptions>
        <reactive>true</reactive>
        <returnResponse>true</returnResponse>
        <useTags>true</useTags>
        <dateLibrary>java8-localdatetime</dateLibrary>
    </configOptions>
</configuration>
```

### How Consumers Import SDKs

Consumers add the SDK as a Maven dependency (version managed by the BOM) and create API beans through a `ClientFactory` in their `-infra` module. See section 6 for the full pattern.

### REST API Conventions

Controllers observed in the codebase follow these patterns:

- Base path: `/api/v1/{resource}`
- Fully reactive: all endpoints return `Mono<ResponseEntity<T>>`
- `@Tag` annotation on every controller for OpenAPI grouping
- `@Operation` annotation on every endpoint with `summary` and `description`
- `@Valid` on request bodies
- UUID-based resource identifiers
- Sub-resources under parent: `/api/v1/customers/{partyId}/addresses/{addressId}`
- Swagger UI at `/swagger-ui.html`, API docs at `/v3/api-docs`
- Packages scanned via `springdoc.packages-to-scan`
- Paths filtered via `springdoc.paths-to-match: /api/**`

## 10. Quick Reference

## 11. SDK Model DTO Patterns

Generated SDK model DTOs (from `openapi-generator-maven-plugin`) have patterns that differ from hand-written DTOs:

### Read-Only ID Fields

ID fields (`dateCreated`, `dateUpdated`, and the entity ID) are **read-only** -- set only via the constructor:

```java
// CORRECT -- set ID via constructor
KycVerificationDTO dto = new KycVerificationDTO(null, null, UUID.randomUUID());

// WRONG -- setter does not exist for ID fields
KycVerificationDTO dto = new KycVerificationDTO();
dto.setKycVerificationId(UUID.randomUUID());  // Compilation error
```

### Fluent Setters for Mutable Fields

Mutable fields use fluent setters (return `this`):

```java
SendNotificationCommand command = new SendNotificationCommand()
    .partyId(partyId)
    .notificationType(SendNotificationCommand.NotificationTypeEnum.WELCOME)
    .channels(List.of(SendNotificationCommand.ChannelsEnum.EMAIL));
```

### Inline Enums

Enum fields generate **inner enum classes** within the DTO:

```java
// NOT a standalone enum -- it is an inner class of the DTO
SendNotificationCommand.NotificationTypeEnum.WELCOME
SendNotificationCommand.PriorityEnum.NORMAL
```

### Getter Naming

Getters use the **full field name** from the OpenAPI spec:
- `getKycVerificationId()` (NOT `getId()`)
- `getVerificationDocumentId()` (NOT `getDocumentId()`)
- `getComplianceCaseId()` (NOT `getCaseId()`)

## 12. Module Dependency Rules

### -web MUST depend on -core

When controllers reference service interfaces, commands, or any types from `-core`, the `-web` module must declare the dependency:

```xml
<!-- {service}-web/pom.xml -->
<dependency>
    <groupId>com.firefly</groupId>
    <artifactId>{service}-core</artifactId>
</dependency>
```

### -interfaces MUST NOT depend on -core

This creates an inverted/circular dependency. Correct direction:

```
-core → -interfaces (core uses DTOs from interfaces)
-web  → -core       (web uses services from core)
-web  → -interfaces (web uses DTOs from interfaces)
```

### NotImplementedException Requires fireflyframework-web

If a `-core` module needs `NotImplementedException` (from `org.fireflyframework.web.error.exceptions`), it must add `fireflyframework-web` as a dependency. The starters `fireflyframework-starter-core` and `fireflyframework-starter-domain` do NOT include it transitively.

### StepStatus Enum Values

The `StepStatus` enum has values: `PENDING`, `RUNNING`, `DONE`, `FAILED`, `SKIPPED`, `TIMED_OUT`, `RETRYING`. `StepStatus.COMPLETED` does **not** exist -- use `StepStatus.DONE`.

## 13. ClientFactory Conventions

In domain services, the `-infra` module wires SDK clients using `ClientFactory` beans:

```java
// CORRECT -- @Component, not @Configuration
@Component
public class KycbClientFactory {
    private final ApiClient apiClient;

    public KycbClientFactory(KycbMgmtProperties props) {
        this.apiClient = new ApiClient();
        this.apiClient.setBasePath(props.getBasePath());
    }

    @Bean
    public KycVerificationApi kycVerificationApi() {
        return new KycVerificationApi(apiClient);
    }
}
```

**Key conventions:**
- Use `@Component` (not `@Configuration`) -- this is a banking platform convention
- Use `@ConfigurationProperties` without `@Configuration` on the properties class (when `@ConfigurationPropertiesScan` is on the application class)
- One `ClientFactory` per downstream service
- One `@Bean` method per API class from the SDK

## 14. Cross-Layer Reflexive Property

When generating code for an upper-layer service (domain/app) that calls a lower-layer service (core), if the lower-layer method does not exist, you MUST create it:

1. Create the endpoint in the core service's `-web` controller
2. Create the service interface and implementation in `-core`
3. Rebuild the core service's SDK
4. Then implement the upper-layer call

**Never** leave upper-layer methods returning static/mock data or empty results. If the core service is not yet available, define a port interface with a stub adapter that throws `NotImplementedException`.

## 15. Javadoc and README Documentation Standards

### Javadoc

All public classes and methods must include Javadoc:

```java
/**
 * Orchestrates KYC verification lifecycle including identity checks,
 * document collection, and compliance case management.
 */
@Service
public class KycServiceImpl implements KycService {

    /**
     * Initiates identity verification for the given compliance case.
     *
     * @param caseId the compliance case identifier
     * @return a {@link SagaResult} with verification outcome
     */
    public Mono<SagaResult> verify(UUID caseId) { ... }
}
```

### README.md

Every microservice MUST have a `README.md` at the project root with:

1. **Service description** -- purpose and role in the platform
2. **Architecture** -- tier, dependencies, how it fits in the broader platform
3. **Module structure** -- table of modules and their purposes
4. **API reference** -- main endpoints with HTTP method, path, and description
5. **Configuration** -- required environment variables and properties
6. **Getting started** -- build and run instructions
7. **Dependencies** -- upstream (calls) and downstream (called by) services

## 16. PII Logging Rules

Never log personally identifiable information. Use resource identifiers instead:

```java
// WRONG
log.info("Processing customer: name={}, email={}", name, email);

// CORRECT
log.info("Processing customer: partyId={}", partyId);
```

## 17. Quick Reference

| Do | Don't |
|----|-------|
| Extend `firefly-parent` as your service's parent POM | Extend `fireflyframework-parent` directly -- you lose BOM imports and OpenAPI profile |
| Use the five-module structure for core services: `-interfaces`, `-models`, `-core`, `-web`, `-sdk` | Put entities in `-core` or controllers in `-interfaces` |
| Use the domain module structure with `-infra` for SDK client factories | Put `ClientFactory` or `@ConfigurationProperties` in `-core` |
| Omit `<version>` for any `com.firefly` dependency -- the BOM manages it | Hard-code versions for platform artifacts |
| Name packages `com.firefly.{tier}.{domain-short}.{layer}` | Use `com.firefly.{full-repo-name}` -- it creates unnecessarily long packages |
| Place DTOs and enums in `-interfaces`, entities in `-models` | Mix DTOs and entities in the same module |
| Generate SDKs from OpenAPI specs using the `-web` module's controllers | Write SDK clients by hand |
| Configure downstream service base paths via `api-configuration.*` in `application.yaml` | Hard-code URLs in Java classes |
| Use `fireflyframework-starter-core` for core services, `fireflyframework-starter-domain` for domain services, `fireflyframework-starter-application` for experience (`exp-*`) and app services | Mix starters across tiers |
| Use CQRS commands/handlers/sagas in domain `-core` modules | Use plain CRUD services in domain modules -- that belongs in core |
| Set `openapi.gen.skip=false` only in the `-web` module | Enable OpenAPI generation in non-web modules |
| Use R2DBC with PostgreSQL for data access in `-models` | Use blocking JDBC -- the platform is fully reactive (WebFlux) |
| Communicate async between domain services via Kafka (Firefly EDA) | Call domain SDKs synchronously for fire-and-forget operations |
| Use environment variables for database credentials (`DB_HOST`, etc.) | Commit credentials in `application.yaml` |
| Place Flyway migrations in `-models/src/main/resources/db/migration` | Place migrations anywhere else |
| Set `spring.application.name` to the repo name (e.g., `core-common-customer-mgmt`) | Use a different naming convention for the Spring app name |
| Use `spring.threads.virtual.enabled: true` | Use thread pools for request handling |
| Expose actuator endpoints: `health`, `info`, `prometheus` | Expose all actuator endpoints in production |
| Disable Swagger UI in `prod` profile | Leave API docs publicly accessible in production |
| Use `@Component` on `ClientFactory` classes | Use `@Configuration` on `ClientFactory` classes |
| Use `@ConfigurationProperties` alone (with `@ConfigurationPropertiesScan`) | Combine `@Configuration` with `@ConfigurationProperties` |
| Use `@Valid @RequestBody` on controller POST/PUT bodies | Omit `@Valid` on `@RequestBody` |
| Use `scanBasePackages = "org.fireflyframework.web"` | Use `scanBasePackages = "com.firefly.common.web"` |
| Use `springdoc.packages-to-scan: *.web.controllers` (plural) | Use `*.web.controller` (singular) |
| Use `health.show-details: when-authorized` | Use `health.show-details: always` |
| Use profile names `dev`, `pre`, `pro` | Use `testing`, `staging`, `local` |
| Use `StepStatus.DONE` | Use `StepStatus.COMPLETED` (does not exist) |
| Set SDK DTO IDs via constructor: `new DTO(null, null, id)` | Use `dto.setId()` -- SDK IDs are read-only |
| Implement full call chain to lower-layer services | Return static/mock data from service methods |
| Log only resource IDs (`partyId`, `paymentId`) | Log PII (names, emails, IBANs, phone numbers) |
| Include Javadoc on all public classes and methods | Skip documentation entirely |
| Include `README.md` in every microservice root | Leave services undocumented |

