awesome-hacks
Docs

サービス・リポジトリ・エンティティとは何か

バックエンド設計の基本用語「サービス(ビジネスロジック)」「リポジトリ」「エンティティ」を、図を使ってゼロから理解する

最終更新:2026/06/16

この記事の目標

バックエンドのコードを読んでいると、ServiceRepositoryEntity という言葉が頻繁に登場する。
これらは「何のために存在するのか」「なぜ分けるのか」を、コード例と図で一から理解する。


全体像:コードの"層"で役割を分ける

バックエンドAPIは、外から来たリクエストに対して「データを調べ、必要な処理をして、結果を返す」という流れで動く。
このとき、処理を役割ごとの層(レイヤー)に分けて書くのが一般的な設計スタイルだ。

graph TD
    Client["クライアント(ブラウザ / アプリ)"]
    Controller["Controller層<br/>(リクエストの受け口)"]
    Service["Service層<br/>(ビジネスロジック)"]
    Repository["Repository層<br/>(DBアクセス)"]
    DB[("データベース")]

    Client -->|HTTPリクエスト| Controller
    Controller -->|処理を依頼| Service
    Service -->|データを要求・保存| Repository
    Repository -->|SQL実行| DB
    DB -->|結果| Repository
    Repository -->|データを返す| Service
    Service -->|結果を返す| Controller
    Controller -->|HTTPレスポンス| Client

各層の責任はシンプルだ。

主な責任
ControllerHTTPリクエストを受け取り、Serviceに処理を渡す。レスポンスを返す
Serviceビジネスロジックを実装する。Repositoryを使ってDBにアクセスする
RepositoryDBへのアクセス(SQL実行)だけを担当する
EntityDBのテーブル構造をコードで表現したもの

エンティティ(Entity)とは

エンティティ = データベースのテーブルをクラスで表現したもの

たとえば users テーブルがあったとする。

CREATE TABLE users (
  id      INTEGER PRIMARY KEY,
  name    TEXT    NOT NULL,
  email   TEXT    NOT NULL UNIQUE,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

このテーブルに対応するエンティティは、TypeScriptであれば次のように書く。

// user.entity.ts
export class User {
  id: number;
  name: string;
  email: string;
  createdAt: Date;
}

エンティティはいわば「テーブルの設計図をコードで写したもの」だ。
DBから取得したデータを入れる器として使い、アプリ内でデータを運ぶ役割を担う。

erDiagram
    users {
        int id PK
        string name
        string email
        timestamp created_at
    }
↑ DBのテーブル定義と、エンティティクラスは1対1に対応する

リポジトリ(Repository)とは

リポジトリ = 特定のテーブル(またはSQLクエリ)に対するDB操作をまとめたクラス

「DBからデータを取る・保存する・更新する・削除する」という操作を、ひとつのクラスに集約する。
Service側がSQLを直接書く必要がなくなり、「何を取得するか」ではなく「取得したデータをどう使うか」に集中できる。

具体例

// user.repository.ts
export class UserRepository {

  // IDでユーザーを1件取得する
  async findById(id: number): Promise<User | null> {
    // ここにSQLやORMの操作を書く
    return db.query(`SELECT * FROM users WHERE id = $1`, [id]);
  }

  // 全ユーザーを取得する
  async findAll(): Promise<User[]> {
    return db.query(`SELECT * FROM users`);
  }

  // ユーザーを新規作成する
  async create(name: string, email: string): Promise<User> {
    return db.query(
      `INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *`,
      [name, email]
    );
  }
}

Repository は「DBとの窓口係」のようなイメージだ。
Service が「ユーザーID:5番のデータをくれ」と言えば、Repository が実際にSQLを実行して返してくれる。

リポジトリの単位

リポジトリは テーブル単位 または データ取得SQL単位 で作成する。

graph LR
    UserRepo["UserRepository<br/>users テーブル担当"]
    OrderRepo["OrderRepository<br/>orders テーブル担当"]
    UserOrderRepo["UserOrderRepository<br/>ユーザーと注文を JOIN するクエリ担当"]

    DB[("DB")]

    UserRepo --> DB
    OrderRepo --> DB
    UserOrderRepo --> DB

テーブルが増えたり、特殊な集計SQLが増えても、それぞれ独立したRepositoryで管理できるため、変更の影響範囲が限定される。


サービス(Service)とは

サービス = ビジネスロジックを実装するクラス

「ビジネスロジック」という言葉が分かりにくければ、「アプリがやるべき仕事の手順」 と理解すると良い。

ビジネスロジックの具体例

「新規ユーザー登録」を考えてみる。やることは単に「DBにINSERTする」だけではない。

  1. 入力されたメールアドレスがすでに登録済みでないか確認する
  2. パスワードをハッシュ化する
  3. 問題なければDBにユーザーを保存する
  4. 登録完了メールを送る

この「手順(ルール)」こそがビジネスロジックだ。

// user.service.ts
export class UserService {
  constructor(
    private readonly userRepository: UserRepository,
    private readonly mailer: MailerService,
  ) {}

  async register(name: string, email: string, password: string): Promise<User> {
    // 1. 重複チェック
    const existing = await this.userRepository.findByEmail(email);
    if (existing) {
      throw new Error('このメールアドレスはすでに使用されています');
    }

    // 2. パスワードのハッシュ化
    const hashedPassword = await bcrypt.hash(password, 10);

    // 3. DBに保存
    const user = await this.userRepository.create(name, email, hashedPassword);

    // 4. メール送信
    await this.mailer.sendWelcomeEmail(user.email);

    return user;
  }
}

「重複チェックしてからINSERTする」「パスワードはハッシュ化する」は、このアプリ固有のビジネスのルールだ。
それをServiceクラスにまとめて書くことで、Controller側はシンプルに「Serviceに処理を投げるだけ」になる。

WebAPIや機能ごとにServiceを作る

サービスは機能・ドメイン単位で分割するのが基本だ。

graph TD
    UserService["UserService<br/>ユーザー登録・更新・削除・検索"]
    OrderService["OrderService<br/>注文受付・キャンセル・履歴参照"]
    NotificationService["NotificationService<br/>メール・プッシュ通知送信"]

    UserService --> UserRepo["UserRepository"]
    OrderService --> OrderRepo["OrderRepository"]
    OrderService --> UserRepo
    NotificationService --> UserRepo

「ユーザーに関すること」は UserService、「注文に関すること」は OrderService というように分けることで、1つのクラスが肥大化するのを防げる。


トランザクションとサービス

複数のDB操作がセットでないと困る場面がある。

例)注文処理
  1. ordersテーブルに注文を追加する
  2. stocksテーブルの在庫を減らす

1だけ成功して2が失敗したら、注文が入ったのに在庫が減らないというバグになる。
「全部成功するか、全部なかったことにするか」を保証する仕組みがトランザクションだ。

トランザクションはServiceで制御するのが原則だ。ServiceはRepositoryを呼ぶ起点であり、「どの操作をひとまとまりにするか」を知っているのがServiceだからだ。

// order.service.ts
async placeOrder(userId: number, itemId: number): Promise<Order> {
  return await this.dataSource.transaction(async (manager) => {
    // トランザクション内で複数の操作を行う
    const order = await this.orderRepository.create(userId, itemId, manager);
    await this.stockRepository.decrement(itemId, manager);
    return order;
  });
  // エラーが起きたら自動的に両方ロールバックされる
}
sequenceDiagram
    participant S as OrderService
    participant OR as OrderRepository
    participant SR as StockRepository
    participant DB as Database

    S->>DB: トランザクション開始
    S->>OR: 注文を作成
    OR->>DB: INSERT INTO orders ...
    S->>SR: 在庫を減らす
    SR->>DB: UPDATE stocks SET quantity = quantity - 1 ...
    Note over DB: 全て成功
    S->>DB: コミット(確定)

    Note over S,DB: もし途中でエラーが出たら
    S->>DB: ロールバック(全操作をなかったことに)

まとめ:3つの役割を一言で

用語一言で言うと
エンティティ(Entity)DBテーブルをコードで表現した「データの器」
リポジトリ(Repository)テーブル単位でDB操作(CRUD)を担当する「DBとの窓口係」
サービス(Service)ビジネスのルール・手順を実装した「アプリの頭脳」。リポジトリを呼び出してDBにアクセスし、トランザクションも管理する
graph TD
    Service["🧠 Service<br/>(ビジネスロジック)<br/>・ルールのチェック<br/>・手順の制御<br/>・トランザクション管理"]
    Repository["🗄 Repository<br/>(DB窓口)<br/>・CRUD操作<br/>・SQL実行"]
    Entity["📦 Entity<br/>(データの器)<br/>・テーブル定義の写し<br/>・データの運び役"]

    Service -->|「このデータを取ってきて」| Repository
    Repository -->|「これがデータです」| Service
    Repository -->|データを入れて運ぶ| Entity
    Entity -->|Serviceで扱う| Service

この3つの分離によって、「DBの接続先が変わってもRepositoryだけ修正すればいい」「ビジネスルールが変わってもServiceだけ直せばいい」という保守しやすい構造になる。