LoginSignup
41
37

More than 1 year has passed since last update.

【Node.js】サーバーサイドディレクトリ構成について可能な範囲でクリーンアーキテクチャっぽくしてみた

Last updated at Posted at 2021-08-27

株式会社Another works CTOの塩原です。
弊社では、現在Node.jsを使ったgRPCサーバーを構築しています。

その中で、現段階でのサーバーサイドアプリケーションのディレクトリ構成を公開したいと思います!

環境

  • gRPC
  • Node.js version 14系
  • Typescript

主なディレクトリー構成図

弊社ではmonorepoを採用しているので、.protoファイルを別ディレクトリーで管理しています。
層としてはapplication, domain, infraの三層構造となっています。

PROJECT
  |- grpc_proto/
  |- project_a/
        |- gen/
      - XXX_pb.d.ts
      - XXX_grpc_pb.js
      - XXX_pb.js
    |-src/
      |- application/
        |- controller/
        |- converter/
        |- grpc/
        |- helper/
      |- consts/ # 定数ファイルをまとめたディレクトリ。どの層からも使うことができる
      |- domain/
        |- converter/
        |- entity/
        |- model/
        |- repository/
        |- service/ 
      |- infra/
        |- s3/
          |- datasource/
          |- entity/
                    |- S3Client.ts     

Application層

主に、client側との接点を持つ層。
protoで生成されたクラスはこの層だけが使うことができる

grpc

エンドポイントごとに作成。
serviceを読んだりする処理などは、controller層で行う。
このディレクトリでは、リクエストを受け取って、レスポンスを返すといった薄い役割のみにしている

controller

grpcのエンドポイントと一対一でクラスを実装する。
serviceクラスを複数実行したり、domainクラスをレスポンス用の型に変換するために、converterを実行したりする。
domain層との接点はこの層が持つ。

Domain層

ビジネスロジックを担当。この層では、grpcの自動生成されたコードをこの層で使うことはできない

entity

serviceクラスのメソッドに対する引数で複雑なものはこちらで定義する
クラスは使わずinterfaceのみとなるので、typesでもいいかもしれない

model

いわゆるドメインモデル
クラスで実装する
サーバーサイドだと、データベースのテーブル型をそのままドメインモデルとすることが多いが(Railsなど)、後述するinfra層のデータアクセス先が増えることを考えて分離した

repository

serviceクラスから使われる。
infra層のdatasourceなどを複数組み合わせて使う。
domainモデルと一対一の関係となっていて、datasourceから返ってくるレスポンスをconverterを使ってdomainモデルに変換して、返す。

service

ビジネスロジックを実装するディレクトリ。
一つのクラスにつき、一つのメソッドとしている
serviceの下にドメインモデルごとにディレクトリーを切る。
一つのサービスが複数のドメインを跨ぐことはあり得るので、その場合は主になるドメインのディレクトリの中に実装する
userを作成する処理の時にuser_profileというドメインモデルがあるとき同じサービスクラス内で処理する場合がある。その場合はuserが主なのでuser/以下にサービスクラスを実装する

domian
  |- service
    |- user
      |- UserUpdateService.ts
            |- UserCreateService.ts
export default class UserUpdateService {
  constructor() {}
  call() {
    // 具体的な処理
  }
}

infra層

外部からデータを取ってくる処理はすべてこちらでwrapする
今回はS3のみだが、Redis, MySQL, 別のマイクロサービスなどなどからデータを取得する時もすべてこの層でwrapする。
データ取得先ごとにディレクトリを切り、その中にそれぞれ、datasourceとentityを作る

infra
  |- redis
    |- datasource
      |- UserRankingRedisDataSource.ts
    |- entity
      |- UserRankingEntity.ts
    RedisClient.ts
  |- s3
    |- datasource
      |- UserS3Datasource.ts
      |- MetaTagS3DataSource.ts 
    |- entity
      |- UserEntity.ts
      |- MetaTagEntity.ts
    S3Client.ts

entity

データ取得先のレスポンスをinterfaceで定義したもの。
またdatasourceに対して、渡すパラメーターなどをinterfaceで定義することもある。
こちらもtypeでもいいかなとは思ってる

datasource

外部からの取得処理を書く。
s3であれば、ある程度のファイルの種類ごとに作ったりする。
databaseから取得する際はこの中でSQLを書いたりするような処理を書く。

データフロー

スクリーンショット 2021-08-26 8.33.48.png

controller -> service

controller層ではprotoから自動で生成されたモデルを扱う
serviceクラスを呼び出す際に、 複雑なデータであればconverterを使って、domain側のentityに変換して渡してあげる
serviceからはdomainモデルで返ってくるので、それをapplication側のconverterを使って、protoから自動生成されたモデルに変換して、grpc層に返す

repository -> datasource

datasourceを実行した時の返り値として、infra層のentityが返ってくるため、converterを使ってdomainモデルに変換をおこなう。

まとめ

今回意識したポイントとして、外部の層と接点を持つ役割を各層一つにするということを意識しました。
application層ならcontroller, domain層ならrepositoryみたいな
converterをいろんなところから呼ばれるとデータ変換がいろんなところで行われてしまうので、それを避けるために、ルートにおかず、層の中に閉じ込めました。

まだまだ、設計で気になる部分はあるので、逐次アップデートを続けていきます。

▼複業でスキルを活かしてみませんか?複業クラウドの登録はこちら!
https://talent.aw-anotherworks.com/?login_type=none

41
37
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
41
37