implementation
Use when implementing any feature, adding code, or modifying existing code in this Kotlin/Spring project. Triggers on write operations like adding entities, services, facades, controllers, or any domain logic.
适合你,如果你需要在Kotlin/Spring项目中添加新功能或修改现有代码
npx oh-my-skill add toongri/oh-my-toong-playground/implementationcurl -fsSL https://oh-my-skill.com/install.sh | bash -s -- toongri/oh-my-toong-playground/implementationnpx oh-my-skill verify toongri/oh-my-toong-playground/implementation怎么用
商店整理自技能原文 · 版本 731a80a · 表述以原文为准当你在Kotlin/Spring项目中编写代码时,Claude会遵循固定的分层架构(Controller→Facade→Service)、DTO转换模式、领域事件规范等生成和修改代码,并确保使用指定的异常类和命名约定。
当你需要实现新功能、添加或修改现有代码时自动触发,特别是涉及实体、服务、外观、控制器等写入操作。
技能原文 SKILL.md
Implementation Guide
Overview
This project follows responsibility assignment, object collaboration, and expression consistency.
Quick Decision
digraph layer {
"Single domain?" [shape=diamond];
"Service" [shape=box];
"Multiple domains?" [shape=diamond];
"Facade" [shape=box];
"HTTP?" [shape=diamond];
"Controller" [shape=box];
"Single domain?" -> "Service" [label="yes"];
"Single domain?" -> "Multiple domains?" [label="no"];
"Multiple domains?" -> "Facade" [label="yes"];
"Multiple domains?" -> "HTTP?" [label="no"];
"HTTP?" -> "Controller" [label="yes"];
}
Critical Rules (Non-negotiable)
1. Controller Flow
ALWAYS: Controller -> Facade -> Service (never Controller -> Service)
@RestController
class ProductV1Controller(
private val productFacade: ProductFacade, // Facade, NOT Service
) : ProductV1ApiSpec
See references/layer-boundaries.md for detailed patterns.
2. Layer Responsibilities
| Layer | @Transactional | Horizontal Dependencies | Why | |-------|---------------|------------------------|-----| | Facade | When atomicity needed | Multiple Services OK | Wraps multiple Services in single transaction | | Service | When atomicity needed | No other Services | Ensures atomicity within single domain |
readOnly usage: Master/Slave DB routing. Use readOnly=true for read-only queries to route to Slave DB.
Facade = COORDINATION ONLY - No business logic (if/when/switch). Delegate to Service/Entity.
See references/layer-boundaries.md for transaction boundaries and anti-patterns.
3. Error Handling
Required: CoreException + ErrorType (single exception type)
throw CoreException(ErrorType.NOT_FOUND, "[id = $id] 엔티티를 찾을 수 없습니다.")
See references/error-handling.md for ErrorType enum and patterns.
4. DTO Flow
Request.toCriteria() -> Criteria.to() -> Command -> Entity -> Info.from() -> Response.from()
See references/dto-patterns.md for complete layer structure.
5. Domain Events
| Requirement | Pattern | |-------------|---------| | Naming | {Action}EventV{n} (version suffix required) | | Interface | Must implement DomainEvent | | Fields | occurredAt: Instant required | | Factory | companion object { fun from(entity) } | | Children | Use snapshots, not entity references |
data class OrderCreatedEventV1(
val orderId: Long,
val items: List<OrderItemSnapshot>, // Snapshot, not entity
override val occurredAt: Instant = Instant.now(),
) : DomainEvent {
companion object {
fun from(order: Order): OrderCreatedEventV1 = OrderCreatedEventV1(...)
}
}
6. EventListener Pattern
| Type | Phase | Error Handling | |------|-------|---------------| | Sync | BEFORE_COMMIT | Failure rolls back tx | | Async | AFTER_COMMIT | try-catch required, log errors |
Always: @TransactionalEventListener(phase = TransactionPhase.XXX) - never plain @EventListener
Logging format: logger.info("[Event] {Action} start/complete - eventType: ${event::class.simpleName}, id: $id")
7. Entity Encapsulation
Seven Rules:
- BaseEntity: ALL entities extend
BaseEntity(provides id, createdAt, updatedAt, deletedAt) - @Table indexes: ALWAYS define indexes for query optimization
- private set: ALL mutable properties
- Behavior methods: State changes via domain verbs (
use(),pay()), not setters - Immutable VOs:
valfields, operations return new instances - Init validation: Validate in
initblock or factory - registerEvent(): Publish events for business-significant state changes
8. Naming Conventions
| Component | Pattern | Example | |-----------|---------|---------| | Controller | {Domain}V{n}Controller | ProductV1Controller | | ApiSpec | {Domain}V{n}ApiSpec | ProductV1ApiSpec | | Facade | {Domain}Facade | ProductFacade | | Service | {Domain}Service | ProductService | | Event | {Action}EventV{n} | OrderCreatedEventV1 | | Query | {Domain}PageQuery | ProductPageQuery |
Methods: Domain verbs (use, expire, cancel), not technical (process, handle, execute)
Variables: Full names (totalAmount, quantity), not abbreviations (amt, qty)
Booleans: is{Adjective}, has{Noun}, canBe{Verb}
9. Domain Purity
Direction: interfaces -> application -> domain <- infrastructure
Domain imports NOTHING from other layers.
| Allowed in Domain | Forbidden in Domain | |------------------|---------------------| | JPA: @Entity, @Table, @Column | @Transactional | | @Component on Service | @JsonProperty, @JsonIgnore | | | Spring Data imports |
Repository Abstraction: Interface in domain, implementation in infrastructure.
10. Null Safety
| Rule | Pattern | |------|---------| | Required fields | Non-nullable (no ?) | | Not found | ?: throw CoreException(ErrorType.NOT_FOUND, "[id = $id] ...") | | Optional | ?.let { }, listOfNotNull() | | Forbidden | !! operator |
11. API Patterns
- ApiSpec interface: Swagger annotations go here, Controller implements it
- Query/PageQuery: Encapsulate pagination with init validation
data class ProductPageQuery(val page: Int, val size: Int) {
init {
require(page >= 0) { "[page = $page] 페이지는 0 이상이어야 합니다." }
require(size in 1..100) { "[size = $size] 페이지 크기는 1~100이어야 합니다." }
}
}
12. Messages
- Error messages: Korean,
[field = $value]prefix AT START - KDoc: Korean
// Correct "[userId = $userId] 사용자를 찾을 수 없습니다." // Wrong (prefix at end) "사용자를 찾을 수 없습니다. [userId = $userId]"
13. Caching
| Rule | Pattern | |------|---------| | Layer | Application Layer(Facade) ONLY | | Pattern | Manual Cache-Aside with CacheTemplate | | Cache Key | Sealed class + TTL embedded | | Cache Model | CachedXxxV1 versioned DTO (never Entity/Response) | | List Caching | IDs only + separate Detail cache | | Invalidation | Domain Event + @TransactionalEventListener(AFTER_COMMIT) |
See references/caching-patterns.md for detailed patterns, examples, and forbidden patterns.
Red Flags (Top 25)
| Thought | Reality | |---------|---------| | "Controller calling Service directly" | Controller -> Facade -> Service is MANDATORY | | "Facade is unnecessary for simple cases" | Facade is ALWAYS required | | "Service calling Service" | Coordinate in Facade | | "Facade->Facade dependency" | Use domain events | | "@Transactional on Service" | Only readOnly or managed in Facade | | "require() is fine" | Use CoreException | | "Domain exception per domain" | Single CoreException + ErrorType | | "Return Entity directly" | DTO layer required | | "English error message" | Korean with [field = $value] prefix | | "Entity without BaseEntity" | ALL entities MUST extend BaseEntity | | "var without private set" | ALL mutable properties need private set | | "@Table without indexes" | ALWAYS define indexes | | "Event without V1 suffix" | Version suffix required | | "Just @EventListener" | Use @TransactionalEventListener with phase | | "Async listener without try-catch" | Async failures must be caught and logged | | "Inject JpaRepository directly" | Define interface in domain | | "@JsonProperty in domain" | JSON is infrastructure concern | | "Nullable for required fields" | Non-nullable by default | | "!! operator" | Use ?: throw CoreException | | "process/handle method names" | Use domain verbs | | "Short variable names (amt, qty)" | Full descriptive names required | | "Business logic in Facade" | Facade coordinates only, logic in Service/Entity | | "External call inside @Transactional" | Use AFTER_COMMIT event listener | | "Entity is just data holder" | Anemic domain model anti-pattern - entities MUST have behavior | | "Skip validation in init" | Invalid objects are forbidden | | "@Cacheable is simpler" | Use CacheTemplate for control | | "Cache in Service/Repository" | Caching belongs in Facade ONLY | | "Cache Response directly" | Use CachedXxxV1 dedicated DTO | | "String cache key" | Use sealed class with TTL | | "@CacheEvict allEntries" | Domain Event + selective evict |
References
Load these files ONLY when working on specific areas:
| File | When to Load | |------|-------------| | references/layer-boundaries.md | Code placement, Service vs Facade, transactions | | references/error-handling.md | Exception creation, validation failures, ErrorType enum | | references/dto-patterns.md | API endpoints, Request/Response/Criteria/Command/Info classes | | references/domain-events.md | Event publishing, EventListener patterns, cross-domain communication | | references/entity-patterns.md | Entity design, encapsulation rules, null safety, domain purity | | references/naming-conventions.md | Method/variable/message naming, Korean messages | | references/api-patterns.md | ApiSpec interface, Query/PageQuery patterns | | references/caching-patterns.md | Cache-Aside in Facade, CacheKey sealed class, CachedXxxV1 DTOs, invalidation |