AgentSkillsCN

project-modules

项目模块结构、架构设计、依赖关系以及新建模块的通用约定

SKILL.md
--- frontmatter
name: project-modules
description: Project module structure, architecture, dependencies, and conventions for creating new modules
triggers:
  - module
  - project structure
  - new module
  - dependency
  - bootstrap
argument-hint: ""

Project Modules & Architecture

Overview

This document describes the project's module structure, dependency rules, and conventions for creating new modules.

Key Principle: Dependencies flow in one direction only. Circular and reverse dependencies are prohibited.

Architecture

This project follows a Layered Architecture.

mermaid
graph TB
    subgraph Bootstrap ["BOOTSTRAP"]
        CommonAPI[common-api-app<br/>Controller → Facade]
        API[skeleton-api-app]
        Worker[skeleton-worker-app]
    end

    subgraph CommonWeb ["COMMON-WEB"]
        Filters[Filters, Interceptors]
        Handler[ExceptionHandler, ApiResource]
    end

    subgraph DomainLayer ["DOMAIN"]
        App[Application<br/>Query / Command]
        Svc[Service<br/>Business Logic]
        Repo[Repository<br/>JPA + QueryDSL]
    end

    subgraph Infra ["INFRASTRUCTURE"]
        Config[JPA Config, QueryDSL Config]
        Cache[Cache, Redis, RestClient]
        Export[Export, Slack]
    end

    subgraph Common ["COMMON"]
        Utils[Codes, Exceptions, Utils, Extensions, Values]
    end

    CommonAPI --> CommonWeb
    CommonAPI --> Infra
    CommonAPI --> DomainLayer
    API --> CommonWeb
    API --> Infra
    Worker --> CommonWeb
    Worker --> Infra
    CommonWeb --> Common
    App --> Svc
    Svc --> Repo
    Infra --> DomainLayer
    DomainLayer --> Common
    Infra --> Common

Module structure

code
modules/
├── common/                     # Common utilities, exceptions, codes, value objects
├── common-web/                 # Web common (Filter, Interceptor, Handler)
├── test-support/               # Test fixtures, REST Docs support
├── domain/                     # Domain models, business rules, JPA Entity, Repository
├── infrastructure/             # Config, Cache, HTTP Client, external services
├── bootstrap/                  # Spring Boot app modules
│   ├── common-api-app/         # Common API server (Controller, Facade)
│   ├── skeleton-api-app/       # API server
│   └── skeleton-worker-app/    # Worker server
└── docs/                       # REST Docs document generation

Module dependencies

mermaid
graph TD
    CommonAPI[common-api-app] --> CW[common-web]
    CommonAPI --> DOMAIN[domain]
    CommonAPI --> INFRA[infrastructure]

    API[skeleton-api-app] --> CW
    API --> INFRA
    API --> DOMAIN

    Worker[skeleton-worker-app] --> CW
    Worker --> INFRA
    Worker --> DOMAIN

    INFRA --> DOMAIN
    INFRA --> COMMON[common]
    DOMAIN --> COMMON
    CW --> COMMON

    style COMMON fill:#e1f5fe
    style INFRA fill:#fff3e0
    style CW fill:#f3e5f5
    style DOMAIN fill:#e8f5e9
    style CommonAPI fill:#ffebee
    style API fill:#ffebee
    style Worker fill:#ffebee

Dependency Direction Rule

IMPORTANT: All inter-module and inter-package dependencies must be unidirectional. Circular and reverse dependencies are prohibited.

  • bootstrap → domain, infrastructure, common-web
  • infrastructure → domain, common
  • domain → common (only)
  • common is the lowest-level module (depends on no other modules)
  • -app modules serve as composition roots that assemble other modules
  • Within domain: dto → entity (DTO knows Entity). Entity must not import DTO

Modules

common

Common utility module. Foundation for all other modules.

PackagePurpose
codes/ResponseCode, ErrorCode, SuccessCode
exceptions/BizException, BizRuntimeException, KnownException
values/Email, PhoneNumber, Money, Rate value objects
utils/datetime/DateFormatter, LocalDateRange, SearchDates
utils/extensions/String masking, DateTime extensions
utils/cipher/AES, SEED encryption
utils/codec/URL encoding/decoding
utils/coroutine/MDC-preserving coroutine utilities

common-web

Web common infrastructure module.

PackagePurpose
filters/AppTraceFilter (UUID v7), ContentCachingFilter
interceptors/LogInterceptor, LogResponseBodyInterceptor
handlers/GlobalExceptionHandlerV2
response/ApiResource, Status, Meta, PageResponse
aop/LogTraceAspect, CheckIpAspect

domain

Domain models, business rules, JPA Entity, Repository. Depends only on the common module.

PackagePurpose
common/entity/BaseEntity, BaseTimeEntity (common audit entities)
common/querydsl/QuerydslRepositorySupport, QuerydslExpressions (QueryDSL common)
{feature}/dto/Domain DTOs (Info, Request, Exception)
{feature}/entity/JPA Entity
{feature}/repository/JpaRepository, QueryRepository
{feature}/service/Business logic
{feature}/application/QueryApplication (query), CommandApplication (create/update/delete)

Request Flow

code
Controller (bootstrap) → Facade (bootstrap)
  → QueryApplication / CommandApplication (domain)
    → Service (domain)
      → JpaRepository / QueryRepository (domain)

infrastructure

Technical implementation module. Config, Cache, HTTP Client, external service integration.

PackagePurpose
client/RestClientConfig, HttpLoggingInterceptor
cache/CacheConfig (Caffeine + Redis)
redis/RedisLockAspect, RedisCacheAspect
persistence/config/JpaConfig (Auditing), QuerydslConfig, DataSourceConfig (Master-Slave routing)
export/Excel/CSV file export (annotation-based, style presets)
slack/Slack SDK-based notifications (Kotlin DSL message builder)

DataSource Routing (Master-Slave)

DataSourceConfig is activated by @ConditionalOnProperty(prefix = "spring.datasource.master.hikari", name = ["jdbc-url"]) and provides Master-Slave routing.

ComponentRole
DataSourceConfigCreates Master/Slave HikariCP pools, auto-activated when property exists
RoutingDataSource@Transactional(readOnly = true) routes to Slave, otherwise routes to Master
LazyConnectionDataSourceProxyDefers connection acquisition until actual query execution to ensure correct routing

DataSource Configuration by Profile

ProfileDBDDL AutoMaster PoolSlave PoolRedisson
embed, localH2 In-Memory (single CP)create-drop- (max 10)-redisson-dev.yml
dev, dev01, dev02MySQL Master-Slavevalidatemax 10, idle 5max 10, idle 5redisson-dev.yml
test, test01, test02MySQL Master-Slavevalidatemax 10, idle 5max 10, idle 5redisson-test.yml
stage, prodMySQL Master-Slavenonemax 20, idle 5max 30, idle 10redisson-prod.yml
  • embed, local: Uses H2 auto-configuration (DataSourceConfig bean is not created)
  • dev, test, stage, prod: spring.datasource.master.hikari.jdbc-url exists, activating DataSourceConfig
  • Aurora MySQL HikariCP settings: max-lifetime: 840000 (14min, for Aurora DNS failover TTL), connection-timeout: 3000 (3s)

bootstrap

Spring Boot Application modules.

ModulePurpose
common-api-appCommon API server (Holiday API, data initialization)
skeleton-api-appREST API server (Controllers)
skeleton-worker-appWorker server (Scheduled Jobs)

test-support

Test fixtures and REST Docs support module.

PackagePurpose
IntegratedTestSupportIntegration test base class
EndPointTestSupportAPI endpoint test base class
RestDocsSupportSpring REST Docs Kotlin DSL support
TestTimeRunnerTest execution time measurement utility

docs

API document generation module based on Spring REST Docs.

  • Generates HTML documents via the Asciidoctor plugin
  • Collects snippets generated from common-api-app and skeleton-api-app tests
  • Generate documents with ./gradlew :modules:docs:docs

Module naming convention

  • -app suffix: Spring Boot executable (bootJar enabled)
  • No suffix: Library module (jar only)

Package naming convention

code
com.myrealtrip.domain.{feature}/
├── dto/
│   ├── {Feature}Info.kt               # Domain DTO
│   ├── Create{Feature}Request.kt      # Domain create request
│   └── {Feature}NotFoundException.kt  # Domain exception
├── entity/
│   └── {Feature}.kt                   # JPA Entity
├── repository/
│   ├── {Feature}JpaRepository.kt      # Spring Data JPA
│   └── {Feature}QueryRepository.kt    # QueryDSL repository
├── service/
│   └── {Feature}Service.kt            # Business logic
└── application/
    ├── {Feature}QueryApplication.kt   # Query use cases
    └── {Feature}CommandApplication.kt # Command use cases

com.myrealtrip.{appname}/
├── api/                # Controllers
├── dto/
│   ├── request/        # API request DTOs
│   └── response/       # API response DTOs
├── facade/             # Facade (DTO conversion + orchestration)
└── config/             # Configuration classes

Creating new module

Domain Feature

code
modules/domain/src/main/kotlin/com/myrealtrip/domain/myfeature/
├── dto/
│   ├── MyFeatureInfo.kt               # Domain DTO
│   └── CreateMyFeatureRequest.kt      # Domain request
├── entity/
│   └── MyFeature.kt                   # JPA Entity
├── repository/
│   ├── MyFeatureJpaRepository.kt      # Spring Data JPA
│   └── MyFeatureQueryRepository.kt    # QueryDSL repository
├── service/
│   └── MyFeatureService.kt            # Business logic
└── application/
    ├── MyFeatureQueryApplication.kt   # Query use cases
    └── MyFeatureCommandApplication.kt # Command use cases

Bootstrap app module

code
modules/bootstrap/my-api-app/
├── build.gradle.kts
└── src/main/kotlin/com/myrealtrip/myapiapp/
    ├── api/
    │   └── MyController.kt
    ├── dto/
    │   ├── request/
    │   │   └── MyApiRequest.kt
    │   └── response/
    │       └── MyApiResponse.kt
    ├── facade/
    │   └── MyFacade.kt
    ├── config/
    │   └── AppConfig.kt
    └── MyApiApplication.kt

HTTP client pattern

Using @HttpExchange

kotlin
@HttpExchange("/todos")
interface TodoClient {
    @GetExchange
    fun findAll(): List<TodoDto>

    @GetExchange("/{id}")
    fun findById(@PathVariable id: Int): TodoDto

    @PostExchange
    fun create(@RequestBody todo: TodoDto): TodoDto
}

Client configuration

kotlin
@Configuration
@ImportHttpServices(group = "mygroup", types = [TodoClient::class])
class TodoClientConfig {

    @Bean
    fun todoClientGroupConfigurer(): RestClientHttpServiceGroupConfigurer {
        return RestClientHttpServiceGroupConfigurer { groups ->
            groups.filterByName("mygroup")
                .forEachClient { _, builder ->
                    builder.baseUrl("https://api.example.com")
                        .requestInterceptor(HttpLoggingInterceptor("TodoClient"))
                }
        }
    }
}

Response format

All API responses follow the ApiResource<T> format.

kotlin
@GetMapping("/{id}")
fun findById(@PathVariable id: Int): ApiResource<Todo> {
    return ApiResource.success(todoService.findById(id))
}

Response structure:

json
{
  "status": {
    "status": 200,
    "code": "SUCCESS",
    "message": "Operation successful"
  },
  "meta": {
    "size": 1
  },
  "data": { ... }
}

Exception handling

Exception types

ExceptionUsageLog Level
KnownExceptionExpected errors (validation, not found)INFO
BizRuntimeExceptionBusiness errors (unrecoverable)ERROR
BizExceptionChecked business exceptionsERROR

Custom exception

kotlin
class TodoNotFoundException(id: Int) : KnownException(
    ErrorCode.DATA_NOT_FOUND,
    "Todo not found: $id"
)

Caching strategy

Two-tier cache

LayerConfiguration
L1 (Caffeine)Local memory, 200 items, 30min TTL
L2 (Redis)Distributed, configurable TTL

Cache names

NameTTLMaxIdleUsage
SHORT_LIVED10min5minSearch, realtime data
DEFAULT30min10minGeneral queries
MID_LIVED1hour20minInfrequently changed data
LONG_LIVED24hour4hourCode tables, settings