C#で業務アプリを作るとき、 「とりあえず動くコード」 から一歩進んで、 「長期運用に耐える設計」 にしておくことが重要です。 この記事では、SOLID原則・DI・レイヤードアーキテクチャ を 現場目線でコンパクトに整理します。
・SOLID原則の“実務で効く”要点
・DI(依存性注入)の考え方と書き方
・レイヤードアーキテクチャの構造(UI / アプリ / ドメイン / インフラ)
・インターフェース設計のコツ
・テストしやすい構造の作り方
・WPF / Web API / コンソールでの適用イメージ
1. SOLID原則を“現場目線”でざっくり押さえる
■ 1-1. S:単一責任の原則(Single Responsibility)
「クラスは1つのことだけをする」 画面制御・DBアクセス・計算ロジックを1クラスに詰め込まない。
■ 1-2. O:開放/閉鎖の原則(Open/Closed)
「拡張に開き、修正に閉じる」 if文を増やすのではなく、クラスを追加して差し替える。
■ 1-3. D:依存性逆転の原則(Dependency Inversion)
「具象ではなく抽象(インターフェース)に依存する」 これが DI とレイヤードの土台になります。
全部を完璧に守る必要はなく、S と D を意識するだけでも設計はかなり良くなります。
2. DI(依存性注入)の基本と実務パターン
■ 2-1. 依存性注入とは
クラスの中で new しないで、
外から必要なオブジェクトを渡してもらう 設計です。
// 悪い例:中で new してしまう
public class OrderService
{
private readonly OrderRepository _repo = new OrderRepository();
}
// 良い例:コンストラクタで受け取る
public class OrderService
{
private readonly IOrderRepository _repo;
public OrderService(IOrderRepository repo)
{
_repo = repo;
}
}
■ 2-2. .NET のDIコンテナを使う(ASP.NET Core / WPFホスト)
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
builder.Services.AddScoped<OrderService>();
これで OrderService を要求すると、自動的に IOrderRepository が注入 されます。
■ 2-3. テストしやすくなる
var mockRepo = new Mock<IOrderRepository>>();
var service = new OrderService(mockRepo.Object);
DBを触らずにユニットテストが書けるようになります。
3. レイヤードアーキテクチャの基本構造
■ 3-1. 典型的な4層構造
- プレゼンテーション層:UI(WPF / WinForms / Web API Controller)
- アプリケーション層:ユースケース(サービス)
- ドメイン層:ビジネスルール(エンティティ・値オブジェクト)
- インフラ層:DB・ファイル・APIアクセス
上の層は下の層に依存するが、逆は依存しない という矢印を意識します。
■ 3-2. 依存関係のイメージ
UI → アプリケーション → ドメイン
↘ インフラ
ドメインはインフラを知らず、インフラがドメインを知る構造にします。
4. インターフェースで“境界”を作る
■ 4-1. リポジトリインターフェース
public interface IOrderRepository
{
Task<Order?> FindAsync(int id);
Task SaveAsync(Order order);
}
■ 4-2. 実装はインフラ層に置く
public class SqlOrderRepository : IOrderRepository
{
private readonly DbConnection _con;
public SqlOrderRepository(DbConnection con) { _con = con; }
public Task<Order?> FindAsync(int id) { ... }
public Task SaveAsync(Order order) { ... }
}
アプリケーション層・ドメイン層は IOrderRepository だけを知り、 SQL Server / SQLite / API などの具体実装は知らない という状態にします。
5. 実務でよくある構成例(Web API)
■ 5-1. Controller → Service → Repository
// プレゼンテーション層(Web API)
[ApiController]
[Route("api/orders")]
public class OrdersController : ControllerBase
{
private readonly OrderService _service;
public OrdersController(OrderService service) { _service = service; }
[HttpPost]
public async Task<IActionResult> Create(CreateOrderRequest req)
{
await _service.CreateAsync(req);
return Ok();
}
}
// アプリケーション層
public class OrderService
{
private readonly IOrderRepository _repo;
public OrderService(IOrderRepository repo) { _repo = repo; }
public async Task CreateAsync(CreateOrderRequest req)
{
var order = new Order(req.CustomerId, req.Items);
await _repo.SaveAsync(order);
}
}
Controller は「HTTPのことだけ」、Service は「ユースケースだけ」、Repository は「DBだけ」を担当します。
6. 実務でよくある構成例(WPF)
■ 6-1. View → ViewModel → Service → Repository
- View:XAML(画面)
- ViewModel:画面ロジック
- Service:ユースケース
- Repository:DB・ファイル
MVVM とレイヤードを組み合わせると、画面とロジックがきれいに分離できます。
7. 設計を“やりすぎない”ための現場ルール
■ 7-1. 小規模なら3層でも十分
- UI
- サービス(ユースケース+ドメイン)
- インフラ
最初から完璧なクリーンアーキテクチャを目指さなくてOK。
■ 7-2. 変更が多いところから分離する
- DBアクセス
- 外部API
- ファイルI/O
ここをインターフェースで切っておくだけでも、後々の変更コストが大きく下がります。
8. 業務アプリ向けベストプラクティス
- SOLIDは「単一責任」と「依存性逆転」を優先して意識する
- DIで new を外に追い出し、テストしやすくする
- UI / アプリ / ドメイン / インフラの役割を分ける
- インターフェースで境界を作り、実装を差し替え可能にする
- 小さく始めて、必要になったところから分割していく
まとめ:C#設計は“分ける・依存を逆転させる・差し替え可能にする”が軸になる
- 1クラスに詰め込まない(単一責任)
- 具象ではなくインターフェースに依存する(DI)
- UI・ロジック・データアクセスをレイヤーで分ける
- テストしやすい構造=長期運用しやすい構造
「最初は小さく、でも後から壊さずに拡張したい」 という現場のニーズに対して、 SOLID × DI × レイヤードは非常に相性の良い組み合わせです。 この記事をベースに、あなたのプロジェクト規模に合った設計レベルを選んでみてください。