• システム開発に関わる内容をざっくりと書いていく

APIサーバ責務の分離:設計例

APIサーバを設計する際、各層の責務を明確に分離することで、保守性や拡張性を向上させることができる。ここでは、APIサーバの代表的な設計パターンを例に、各層の役割とその連携の流れを解説する。

1. Httpリクエストとレスポンスの処理:コントローラ・ハンドラー

コントローラハンドラーは、クライアントからのHttpリクエストを受け取り、適切なレスポンスを返す役割を担う。これらは通常、リクエストのルーティングやバリデーションを行い、次の層(サービス層)に処理を委譲する。

  • コントローラ: URLに基づいて処理を分岐し、必要なサービスを呼び出して、レスポンスを返す。
  • ハンドラー: より具体的な処理を担当し、各エンドポイントに応じたリクエストを処理する。

ポイント:

  • 複雑なロジックはコントローラに書かず、ロジックをサービスに委譲することで、コントローラはリクエストの受付とレスポンスの送信に集中させる。
csharp
public class UserController : ControllerBase
{
private readonly IUserService _userService;

public UserController(IUserService userService)
{
_userService = userService;
}

[HttpGet("{id}")]
public async Task<IActionResult> GetUser(int id)
{
var user = await _userService.GetUserById(id);
if (user == null)
{
return NotFound();
}
return Ok(user);
}
}

2. アプリケーションロジック・ビジネスロジック:サービス層

サービス層は、アプリケーション固有のビジネスロジックやアプリケーションロジックを集約する役割を持つ。データの加工やビジネスルールの実行など、コントローラから受け取ったリクエストに対して、必要な処理を実行する。

  • 役割:
    • 複数のリポジトリからデータを取得し、それを加工する。
    • ビジネスルールに従って、処理を行う。
    • 外部APIの呼び出しなど、外部接続も行う。

サービス層は、外部とのインターフェースとして機能するが、具体的なデータ操作や外部接続のロジックはリポジトリに委譲する。

csharp
public class UserService : IUserService
{
private readonly IUserRepository _userRepository;

public UserService(IUserRepository userRepository)
{
_userRepository = userRepository;
}

public async Task<User> GetUserById(int id)
{
var user = await _userRepository.FindById(id);
if (user == null)
{
throw new NotFoundException("User not found");
}
// ビジネスロジックを実行(必要に応じて)
return user;
}
}

3. DBや外部接続の処理:リポジトリ層

リポジトリ層は、データベースや外部APIなどのデータソースに対するアクセスを抽象化する役割を担う。これにより、データの永続化や取得のロジックがサービス層に露出しないようにする。

  • 役割:
    • データベースへのクエリ実行。
    • 外部サービスやAPIとの通信。
    • データの永続化、更新、削除。

リポジトリは、外部接続に特化しており、複雑なビジネスロジックを持たない。サービス層やコントローラ層からの要求に対して、必要なデータ操作を行い、結果を返す。

csharp
public class UserRepository : IUserRepository
{
private readonly AppDbContext _context;

public UserRepository(AppDbContext context)
{
_context = context;
}

public async Task<User> FindById(int id)
{
return await _context.Users.FindAsync(id);
}
}

4. 呼び出しの順序(参照)

APIサーバの各層の呼び出し順序は以下の通り:

  1. コントローラ/ハンドラー: リクエストを受け取り、サービスに処理を委譲する。
  2. サービス層: ビジネスロジックやアプリケーションロジックを実行し、リポジトリにデータアクセスを依頼する。
  3. リポジトリ層: データベースや外部サービスに接続し、データを取得・更新する。

呼び出しの流れは以下のようになる:

  • コントローラサービスリポジトリ

5. その他の要素

  • モデル: 各層で使用されるオブジェクト。例えば、ユーザーデータや製品情報など、アプリケーションで扱うエンティティを定義する。
csharp
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
  • 共通処理ロジック: よく使うロジックを共通化するために、静的クラスやユーティリティクラスを作成することがある。共通処理は再利用性を高め、重複コードを減らす。
  • 定数やenum: アプリケーション全体で共通する定数や、状態を表すためのenumは、定義ファイルにまとめておくことでコードの可読性や保守性を向上させる。
csharp
public static class Constants
{
public const string AdminRole = "Admin";
}
csharp
public enum UserStatus
{
Active,
Inactive,
Suspended
}

まとめ

APIサーバの責務を分離することで、各層が担う役割が明確になり、保守性や拡張性が大幅に向上する。コントローラはリクエスト処理に集中し、サービス層はビジネスロジックを扱い、リポジトリ層はデータアクセスを抽象化する。この分離により、コードがよりモジュール化され、変更や拡張が容易になる。また、共通処理や定数の適切な管理も忘れずに行うことで、コードベース全体の品質を向上させることができる。