Guidance for working with the DCD codebase.
DCD is a Spring Boot REST API (Kotlin, Java 17) managing Docker-based applications. Supports multiple frameworks (Spring Boot, Django, etc.), Docker container orchestration, and application lifecycle management. Infrastructure: MariaDB, Redis (caching/rate limiting), Docker Java client.
src/main/kotlin/com/dcd/server/
├── core/domain/ # Business logic & use cases
│ ├── {feature}/usecase/ # *UseCase interfaces
│ ├── {feature}/spi/ # Ports (QueryPort, CommandPort)
│ ├── {feature}/dto/ # Data transfer objects
│ ├── {feature}/exception/ # Custom domain exceptions
│ └── {feature}/model/ # Domain models
├── persistence/ # SPI implementations (adapters)
│ └── {feature}/\*PersistenceAdapter.kt
├── presentation/ # HTTP adapters (REST controllers)
│ └── {feature}/\*WebAdapter.kt
└── infrastructure/ # Spring config, third-party clients
├── global/config/ # Security, WebSocket, Caching
├── global/security/ # JWT, authentication
└── global/thirdparty/ # Docker, Git, Mail clients
Dependency Flow:
Presentation (Request) → .toDto() → ReqDto
↓
UseCase (domain logic)
↓
ResDto → .toResponse() → Response (Presentation)
Ports & Adapters Flow:
UseCase → Port (interface) ← Adapter (implementation)
├── *PersistenceAdapter (persistence layer)
└── *Adapter (infrastructure layer)
Each Feature Has:
*UseCaseinterface + implementation incore.domain.{feature}.usecase*Portinterfaces (Query/Command) incore.domain.{feature}.spi*Request/*Responseobjects inpresentation.{feature}.data.request/response(API contracts)*ReqDto/*ResDtoincore.domain.{feature}.dto(UseCase contracts).toDto()extension to convert Request → ReqDto.toResponse()extension to convert ResDto → Response*PersistenceAdapterinpersistence.{feature}implementing ports*Adapterininfrastructure.{feature}orinfrastructure.globalimplementing ports*WebAdapter(REST controller) inpresentation.{feature}
Build & Test:
./gradlew clean build # Full build + tests
./gradlew build -x test # Build without tests
./gradlew test --tests "*ApplicationWebAdapterTest"
./gradlew test --tests "*ApplicationWebAdapterTest.createApplication*"Run Application (port 8081):
# One-time setup
docker network create dcd
# Start infrastructure + app
docker-compose up -d # MariaDB, Redis, Nginx
./gradlew bootRunCode Quality: Kotlin 1.8.22 + strict JSR305 annotations enforced by compiler.
Environment Variables (required):
- Database:
DB_URL,DB_USER,DB_PASSWORD,DB_DRIVER,DB_HIBERNATE_DIALECT - Redis:
REDIS_HOST,REDIS_PORT,REDIS_USERNAME,REDIS_PASSWORD - JWT:
JWT_ACCESS_SECRET,JWT_REFRESH_SECRET,JWT_ACCESS_TIME,JWT_REFRESH_TIME - Security:
AES_SECRET,AES_INIT_VECTOR - Mail:
MAIL_ADDRESS,MAIL_PASSWORD - Docker:
DOCKER_REGISTRY_URL,DOCKER_TOKEN - Paths:
DOMAIN_CONFIG_PATH
Files: src/main/resources/application.yml (main), application-test.yml (test profile), docker-compose.yml (infrastructure).
Style: BehaviorSpec with given/when/then. Location: src/test/kotlin/ mirrors source structure.
class ApplicationWebAdapterTest : BehaviorSpec({
val mockUseCase = mockk<SomeUseCase>()
val adapter = WebAdapter(mockUseCase)
given("Setup") {
`when`("Action") {
every { mockUseCase.execute(any()) } returns expectedResult
val result = adapter.someMethod()
then("Expectation") {
result shouldBe expected
verify { mockUseCase.execute(any()) }
}
}
}
})Key: Mock only UseCases (in *WebAdapterTest). Integration tests hit real DB via *PersistenceAdapterTest.
Domain defines interfaces, infrastructure implements:
*Port(query/command operations) incore.domain.{feature}.spi*PersistenceAdapterimplements inpersistence- Enables testing with mocks, swapping implementations
Annotations handle authorization, rate limiting, validation:
@Limit→LimitAspect(rate limiting via bucket4j)@WorkspaceOwnerVerification→OwnerValidateAspect@CheckEmailCertificate→EmailCertificateAspect@Lock→ distributed locking
Use for validation logic that doesn't belong in UseCases.
Extend BasicException in core.domain.{feature}.exception:
- Application layer catches and translates to HTTP responses
- Clear error propagation path
Application lifecycle events (e.g., DeployApplicationEvent) trigger side effects. Publish from UseCases, handle in listeners.
Presentation Layer Flow:
- Request: Client →
*Requestobject →.toDto()→*ReqDto→ UseCase - Response: UseCase →
*ResDto→.toResponse()→*Responseobject → Client
Conversion Layer:
*Request(API input contract) inpresentation.{feature}.data.request*Response(API output contract) inpresentation.{feature}.data.response*ReqDto(UseCase input) incore.domain.{feature}.dto*ResDto(UseCase output) incore.domain.{feature}.dto
Rules:
- Never serialize entities directly; always use Request/Response
- UseCase always works with ReqDto/ResDto, never Request/Response
- Extensions
.toDto()and.toResponse()handle conversions
Ports can be implemented in infrastructure layer for cross-cutting concerns:
| Feature | Library | Port Implementation | Location |
|---|---|---|---|
| Authentication | Spring Security + JWT (JJWT) | GenerateTokenPort, ParseTokenPort → GenerateTokenAdapter, ParseTokenAdapter |
infrastructure.global.jwt.adapter |
| Security | Spring Security | SecurityPort → SecurityAdapter |
infrastructure.global.security |
| Encryption | AES | EncryptPort → EncryptAdapter |
infrastructure.global.security |
| Database | Spring Data JPA + Hibernate, MariaDB | *Port → *PersistenceAdapter |
persistence.{feature} |
| Caching | Spring Cache + Redis (Redisson) | Configured in infrastructure.global.config |
infrastructure.global.config |
| Rate Limiting | bucket4j + Redis | LimitPort → RedisLimitAdapter, LocalLimitAdapter |
infrastructure.global.thirdparty.bucket4j |
| WebSocket | Spring WebSocket | WebSocket message handling | infrastructure.global.config.WebSocketConfig |
| Container Management | docker-java | *Adapter for Docker operations |
infrastructure.domain.application.adapter |
| Version Control | JGit | *Adapter for Git operations |
infrastructure.domain.application.adapter |
| Locking | Redisson | @Lock AOP |
infrastructure.global.aop |
| Token Blacklist | Redis | TokenBlackListPort |
infrastructure.global |
Follow this complete structure:
-
Domain (
core/domain/{feature}/)usecase/*UseCase.kt(interface + impl)spi/*Port.kt(Query/Command interfaces)dto/*ReqDto.kt,*ResDto.ktexception/*Exception.kt(extend BasicException)model/(domain models)
-
Presentation (
presentation/{feature}/)data/request/*Request.kt(API input contracts)data/response/*Response.kt(API output contracts)data/extension/*Extension.kt(.toDto(), .toResponse() converters)*WebAdapter.kt(REST controller)- Call: Request → .toDto() → UseCase → .toResponse() → Response
-
Persistence (
persistence/{feature}/)domain/*Entity.kt(JPA entity)*Repository.kt(JPA repository)*PersistenceAdapter.kt(implements Port)
-
Infrastructure (conditional, if needed)
infrastructure/{feature}/adapter/*Adapter.kt(implements Port)- Or
infrastructure/global/adapter/*Adapter.kt(cross-cutting) - Examples: Token generation, encryption, external APIs, caching
-
Tests (mirror structure in
src/test/kotlin/)*WebAdapterTest(mock UseCase)*PersistenceAdapterTest(real DB via H2/test profile)
Ports Can Be Implemented In:
persistence.{feature}.*PersistenceAdapter(database operations)infrastructure.{feature}.adapter.*Adapter(feature-specific external services)infrastructure.global.adapter.*Adapter(shared services: JWT, encryption, rate limiting)