この記事はラクス Advent Calendar 2025の24日目の記事です。![]()
![]()
![]()
![]()
![]()
![]()
![]()
はじめに
設計の話をしていると、「それは責務が違うよね」という言葉をよく聞きます。一方で、それは答えられなければいけない問いでもあります。
この記事では、私が日々コードを読んでいて「これはよく見るな」「判断に迷いやすいな」と感じるコード例をもとに、
責務をどう捉えるか
どんな観点で判断しているか
を整理して書いてみます。
Serviceは薄ければ良いのか
@Service
public class OrderService {
public Order create(CreateOrderCommand cmd) {
Order order = Order.create(cmd);
orderRepository.save(order);
return order;
}
}
よく見かける、きれいなServiceです。Serviceは調整役、ロジックはEntity側、という分け方もよく知られています。
このような形を見ると、次の点で判断に迷うことがあります。
-
CreateOrderCommandはどの層の知識なのか -
Order.createはどこまでの判断を持ってよいのか
このようなコードに対しては、Serviceが薄いかどうかではなく、
- なぜServiceにロジックがないのか
- なぜEntityに寄せたのか
を説明できるかどうかを、責務分離の判断軸として考えることが多いです。
薄いアプリケーションサービスを見たとき
public void ship(ShipCommand cmd) {
Order order = orderRepo.find(cmd.orderId());
order.ship();
orderRepo.save(order);
}
処理は短く、役割も明確そうに見えます。
このような形を見ると、次の点が判断のポイントになります。
- このクラスがなくなったら何が困るのか
- ユースケースの意図はどこに表れているか
薄いかどうかではなく、存在理由を説明できるかどうかを判断軸として考えることが多いです。
Entityは「今」を知るべきか
public class Order {
public static Order create(CreateOrderCommand cmd) {
return new Order(cmd.items(), Clock.systemDefaultZone().instant());
}
}
ドメインにロジックがあり、一見すると自然な設計です。
一方で、このような実装を見ると次の点が判断のポイントになります。
- Entityが現在時刻を直接知る必要があるのか
- テストでこの時間をどう扱うのか
このような場合、「ロジックがどこにあるか」よりも、
- 時間という知識を誰が持つべきか
- その依存をどこまで許容するか
を責務分離の判断軸として考えることが多いです。
Repositoryは何を返すべきか
Optional<User> findActiveByEmail(String email);
このメソッド名を見ると、次の点が判断のポイントになります。
-
Activeとは何を意味しているのか - それはDBの都合か、ビジネスルールか
このようなケースでは、Repositoryが返しているのが
- 単なる状態なのか
- 意味まで含んでいるのか
を意識して見ることが多いです。意味を返し始めると、変更理由が混ざりやすくなるためです。
値オブジェクトにどこまで持たせるか
public record Price(int value) {
public boolean isFreeShipping() {
return value >= 5000;
}
}
値オブジェクトに振る舞いを持たせる設計もよく見かけます。
このような場合、次の点が判断のポイントになります。
- この概念は単独で意味が完結しているか
- 条件が増えたとき、どこが影響を受けるか
価格という値だけで完結しているのであれば問題ありませんが、関心が横に広がりそうな場合は、責務を分ける判断をすることが多いです。
イベントで分けたあとに残るもの
order.cancel();
eventPublisher.publish(new OrderCanceled(order.getId()));
イベントを使うと処理はきれいに分離されます。
一方で、このような形を見ると次の点が判断のポイントになります。
- イベントが発行された時点で何が完了したのか
- 失敗した場合の責任はどこにあるのか
イベントで処理を分けたあとも、「完了とは何か」という責務は残ります。その責務をどこで担保しているかを意識して見ることが多いです。
外部APIと状態変更を並べたとき
public void pay(Order order) {
gateway.charge(order.total());
order.markPaid();
}
このようなコードを見ると、次の点が判断のポイントになります。
- 何が成功したら「支払った」と言えるのか
- 変更理由は1つなのか
外部APIとのやり取りとドメインの状態変更が、
- 同じ理由で変更されるのか
- 別々に変更されそうか
という観点で、責務を分けるかどうかを考えることが多いです。
Specificationは何を表すものか
public class AdultUserSpec {
public boolean isSatisfiedBy(User user) {
return user.getAge() >= 18;
}
}
Specificationという名前がつくと、設計として整って見えます。
このような場合、次の点が判断のポイントになります。
- この条件は1つの意味として読めるか
- 条件が増えたときに理解しやすいか
Specificationが意味のある条件のまとまりとして読めるかどうかを責務分離の観点で見ることが多いです。
Clockを引数に渡すという選択
public void ship(Clock clock) {
this.shippedAt = clock.instant();
}
テストは書きやすくなります。
一方で、このような設計を見ると次の点が判断のポイントになります。
- 呼び出し側の責務は増えていないか
- APIとして自然か
テストのしやすさと使いやすさのバランスをどこで取っているかを意識して見ることが多いです。
分けた結果、何が残ったか
PriceCalculator
TaxCalculator
InvoiceGenerator
InvoiceService
責務を分けること自体は目的ではありません。
このような構成を見ると、次の点が判断のポイントになります。
- なぜこの単位で分けたのか
- 読む人の理解は楽になったか
分け方そのものではなく、理解しやすくなっているかどうかを責務分離の観点で考えることが多いです。
おわりに
本記事では、責務分離についての「正解」を提示することはしていません。それは、責務の切り方が設計パターンやレイヤ構成だけで決まるものではなく、置かれている文脈や将来の変更によって常に揺れ動くものだと考えているためです。
私自身は、責務を「どこに置くべきか」ではなく「なぜそこに置いたのかを説明できるか」という観点で捉えることが多いです。その説明が、その時点だけでなく、変更が入ったときにも破綻しないかどうかを特に意識しています。
具体的には、変更理由が混ざっていないか、その知識を持つことで、どこが楽になり、どこが重くなるのかといった点を判断軸として設計を考えています。ロジックの所在やクラスの薄さ・厚さは、その結果として現れるものだと思っています。
本文中で挙げたコード例も、「この形が正しいかどうか」を問うためのものではなく、自分ならどう判断するか、どこで迷うか を考えるための題材として挙げました。
責務分離に唯一の答えはありませんが、どんな観点で判断しているのか、なぜその判断に至ったのかを言葉にできる状態にしておくことは、設計を考える上で大切だと感じています。
この記事が、責務について考える際の視点や対話のきっかけになれば幸いです。