SuperAPI AI Directory

repository-design

Coding & development

View repository

Updated 10-15 23:39

About this skill

Repository Design

A repository is a responsibility specialized for I/O of an aggregate.

Design Principles

Naming Conventions

The repository name must be 集約名 + Repository .

# NG: テーブル名ベース
OrdersTableRepository
UserAccountsRepository  (テーブル名 user_accounts に由来)

# NG: DTOベース
OrderDtoRepository
UserResponseRepository
OK: Based on aggregate name

OrderRepository UserRepository


**検出基準**: リポジトリ名に `Table`, `Dto`, `Entity`, `Record`, `Row` 等のインフラ用語が含まれている。

### CQS(Command Query Separation)

リポジトリのメソッドはCQSに従う。各メソッドはコマンド(状態変更、戻り値なし)またはクエリ(状態変更なし、値を返す)のいずれかである。

**Query(問い合わせ)**: 集約を返す。副作用なし。

**Command(命令)**: 集約を受け取り、voidを返す。状態を変更する。

### 単件・複数件I/O

**単件I/O**:

```kotlin
// Query: 単件取得
fun findById(id: OrderId): Order?

// Command: 単件保存
fun store(order: Order)

Multiple-item I/O:

// Query: 複数件取得
fun findByIds(ids: List<OrderId>): List<Order>

// Command: 複数件保存
fun storeMulti(orders: List<Order>): Int

Synchronous/Asynchronous Patterns

Repositories can be designed as either synchronous or asynchronous. For errors, choose either the exception approach or the Result/Either approach. For detailed design guidelines on the error approach, refer to the error-handling skill.

Synchronous (Exception Approach):

// Query: 例外でエラーを通知
fun findById(id: OrderId): Order?

// Command
fun store(order: Order)

Synchronous (Result/Either Approach):

// Query: Result型でエラーを返す
fun findById(id: OrderId): Result<Order?, RepositoryError>

// Command
fun store(order: Order): Result<Unit, RepositoryError>

Asynchronous (Future):

// Query: Futureのエラー機構を使用
def findById(id: OrderId): Future[Option[Order]]

// Command
def store(order: Order): Future[Unit]

Asynchronous (async/await + Result):

// Query
async fn find_by_id(&self, id: &OrderId) -> Result<Option<Order>, RepositoryError>;

// Command
async fn store(&self, order: &Order) -> Result<(), RepositoryError>;

Anti-patterns

The return value of findById is not an aggregate

// NG: DTOを返す
fun findById(id: OrderId): OrderDto

// NG: テーブル行を返す
fun findById(id: OrderId): OrderRecord

// NG: エンティティの一部を返す
fun findById(id: OrderId): OrderSummary

// OK: 集約を返す
fun findById(id: OrderId): Order?

Method names containing domain logic

A repository is responsible only for I/O of an aggregate (save, retrieve, delete). Domain-specific operations must not be included in method names.

// NG: ドメインロジックがリポジトリに漏れている
fun leave(userId: UserId)          // 「退会」はドメインの振る舞い
fun activate(orderId: OrderId)     // 「有効化」はドメインの振る舞い
fun cancel(orderId: OrderId)       // 「キャンセル」はドメインの振る舞い
fun approve(requestId: RequestId)  // 「承認」はドメインの振る舞い
fun rename(userId: UserId)         // 「名前の変更」はドメインの振る舞い

// NG: DB操作を想起するメソッド名(リポジトリはDB以外の実装もありえるため)
fun insert(order: Order)             // INSERT文を連想
fun update(order: Order)             // UPDATE文を連想
fun select(id: OrderId): Order?      // SELECT文を連想
fun upsert(order: Order)             // UPSERT文を連想

// OK: コレクションとしてのI/O操作
fun store(user: User)
fun put(order: Order)
fun add(order: Order)
fun delete(order: Order)
fun findById(id: OrderId): Order?
fun storeMulti(orders: List<Order>): Int
fun putAll(orders: List<Order>)
fun addAll(orders: List<Order>)
fun findByIds(ids: List<OrderId>): List<Order>

Allowed method names:

  • Single-item
    • store
    • findById
    • delete
    • put
    • remove
    • add
  • Plural
    • storeMulti
    • findByIds
    • deleteMulti
    • putMulti
    • addMulti

and similar I/O operations.

Forbidden method names:

  • Domain terms: leave, activate, cancel, approve , etc.
  • DB operation terms: insert, update, select, upsert , etc.

Correct design: Domain logic is executed by aggregate methods, and the repository merely saves the result.

// 集約でドメインロジックを実行
val user = userRepository.findById(userId)
val leftUser = user.leave()  // 集約のメソッド

// リポジトリは保存するだけ
userRepository.store(leftUser)

Calling another repository from a repository

A repository must not call another repository within its implementation. Coordination between aggregates is the responsibility of the use case layer (application service).

Reason:

  • Violation of aggregate boundaries: One repository per aggregate. A dependency on another repository is a sign that aggregate boundaries are not designed correctly
  • Mixing of responsibilities: A repository's responsibility is "persistence and restoration of an aggregate," and retrieving other aggregates is not included in that responsibility
  • Hidden coupling: Dependencies between aggregates become buried in the infrastructure layer, making them difficult to discover and change
  • Testing difficulty: Testing a repository requires mocks of another repository
// NG: リポジトリが別のリポジトリに依存
class OrderRepositoryImpl(
    private val productRepository: ProductRepository
) : OrderRepository {
    override fun store(order: Order) {
        val product = productRepository.findById(order.productId)
        // ...
    }
}

// OK: ユースケース層で調整
class PlaceOrderUseCase(
    private val orderRepository: OrderRepository,
    private val productRepository: ProductRepository,
) {
    fun execute(command: PlaceOrderCommand) {
        val product = productRepository.findById(command.productId)
        val order = Order.create(product.id, command.quantity)
        orderRepository.store(order)
    }
}

Detection criteria: Another repository is referenced in the repository's constructor or methods.

Violations of store's arguments/return values

// NG: DTOを受け取る
fun store(dto: OrderDto)

// NG: 保存後のDBレコードを返す(CQS違反)
fun store(order: Order): OrderRecord

// NG: IDを返す(CQS違反)
fun store(order: Order): OrderId

// OK: 集約を受け取り、voidを返す
fun store(order: Order)

Check procedure

  1. Identify the repository's interface/trait
  2. Check whether the name is 集約名 + Repository check whether it is
  3. Check whether each method follows CQS
    • Query: returns an aggregate, no side effects
    • Command: receives an aggregate, returns void
  4. check whether the input/output types are aggregates
  5. if there are violations, present concrete correction proposals

Related skills (recommended to read together)

When using this skill, also refer to the following skills:

  • repository-placement: Placement location of repository interfaces (use case layer vs. domain layer)
  • aggregate-design: Design rules for aggregates persisted by repositories
  • error-handling: Error handling approach in synchronous/asynchronous repository patterns