📚
📝 アプリケーションサービスをPythonで学ぶ
はじめに
ドメイン駆動設計 (DDD) の書籍に出てくる 「アプリケーションサービス」 という概念。抽象的な説明だけでピンとこないという読者も多いはずです。
この記事では、Pythonで手を動かしながら理解できるように解説します。
アプリケーションサービスとは?
こんな説明がよくあります:
- アプリケーションサービスは ユースケースの窓口
- ドメイン(ビジネスルール)の呼び出しをまとめる場所
- 永続化や外部システムとの連携はリポジトリに委譲する
つまり、「何をどう順番にやるか」を組み立てる層です。
ここでは 図書館の貸出 を題材にします。
3層の役割分担
DDDの流れを図で表すとこんな感じ:
- UI/Controller: ユーザーやAPIリクエストから呼ばれる入口
- アプリケーションサービス: ユースケースを実現するための手順を記述
- ドメインモデル: ビジネスルールの本体
- リポジトリ: 保存や検索の詳細を隠蔽
Python で実装してみよう
ファイル構成は下記の様になる様にします。
% tree ./
./
├── application_service.py
├── domain.py
├── main.py
└── repository.py
1 directory, 4 files
ドメインモデル
domain.py
from dataclasses import dataclass
from datetime import date
@dataclass(frozen=True)
class Book:
id: str
title: str
is_borrowed: bool = False
def mark_as_borrowed(self) -> "Book":
if self.is_borrowed:
raise ValueError("すでに貸出中です")
return Book(id=self.id, title=self.title, is_borrowed=True)
@dataclass(frozen=True)
class Member:
id: str
name: str
@dataclass(frozen=True)
class Loan:
book_id: str
member_id: str
borrowed_on: date
ポイント:
- ルールをモデルに閉じ込める(貸出中ならエラー)
- 不変オブジェクトにして安全に状態遷移
リポジトリ(保存場所)
repository.py
class InMemoryBookRepository:
def __init__(self):
self._store = {}
def find_by_id(self, book_id):
return self._store.get(book_id)
def save(self, book):
self._store[book.id] = book
def list_all(self):
return list(self._store.values())
class InMemoryLoanRepository:
def __init__(self):
self._store = []
def save(self, loan):
self._store.append(loan)
def list_by_member(self, member_id):
return [l for l in self._store if l.member_id == member_id]
アプリケーションサービス(ユースケースの窓口)
application_service.py
from datetime import date
from domain import Loan
class LibraryAppService:
def __init__(self, books, loans):
self.books = books
self.loans = loans
def borrow_book(self, book_id, member_id):
book = self.books.find_by_id(book_id)
if book is None:
return {"ok": False, "message": "本が見つかりません"}
try:
updated = book.mark_as_borrowed()
except ValueError as e:
return {"ok": False, "message": str(e)}
self.books.save(updated)
loan = Loan(book_id=book_id, member_id=member_id, borrowed_on=date.today())
self.loans.save(loan)
return {"ok": True, "message": "貸出しました", "loan": loan}
実行用コード
main.py
from repository import InMemoryBookRepository, InMemoryLoanRepository
from applicationservice import LibraryAppService
from domain import Book, Member
if __name__ == "__main__":
books = InMemoryBookRepository()
loans = InMemoryLoanRepository()
app = LibraryAppService(books, loans)
books.save(Book(id="b1", title="Python入門"))
member = Member(id="m1", name="Alice")
print(app.borrow_book("b1", member.id)) # 成功
print(app.borrow_book("b1", member.id)) # 失敗(すでに貸出中)
## 実行例
% python ./main.py
{'ok': True, 'message': '貸出しました', 'loan': Loan(book_id='b1', member_id='m1', borrowed_on=datetime.date(2025, 9, 25))}
{'ok': False, 'message': 'すでに貸出中です'}
まとめ
- アプリケーションサービスは「ユースケースの窓口」
- ドメインモデルにルールを閉じ込め、リポジトリで保存を隠す
- Pythonで手を動かせば「Scala本の抽象的な説明」が腑に落ちる
DDDの実践は「分けて考える」ことが肝心です。
Discussion