24
18

More than 1 year has passed since last update.

弊社プロダクトのディレクトリ構成を公開します!(Node.js X Express)

Last updated at Posted at 2021-11-18

初めまして、株式会社Another works CTOの塩原です。
弊社では、複業クラウドというサービスを運営しており、今回はそのサービスのAPI側の設計を公開したいと思います。

レイヤー

レイヤーは三層構造でdomainレイヤー、applicationレイヤー、othersレイヤーという風にしています。
それぞれ外側への依存を禁止するような構成にしています。
レイヤーの設計はこの記事を参考にしています。ref: https://nrslib.com/adop/#outline__3_3_2

スクリーンショット 2021-11-13 11.33.51.png

依存関係

スクリーンショット 2021-11-18 18.35.24.png

ディレクトリ構成

スクリーンショット 2021-11-13 20.46.54.png

コーディング規約

typescriptを使っているので、typescriptのコーディング基準に則っています
https://typescript-jp.gitbook.io/deep-dive/styleguide#filename

命名規則

eslintで縛っています

'@typescript-eslint/naming-convention': [
  "error",
  {
   "selector": "default",
   "format": ["camelCase"]
  },
  {
    "selector": "typeLike",
    "format": ["PascalCase"]
   },
  {
   "selector": "class",
   "format": ["PascalCase"]
  },
  {
   "selector": "interface",
    "format": ["PascalCase"]
  },
  {
   "selector": "typeAlias",
   "format": ["PascalCase"]
  },
  {
   "selector": "objectLiteralProperty",
   "format": null
   }
],

Type vs Enum

Typeを使用する
Enumは使用しない

Typeの書き方

export type CompanyStatus = 'init' | 'active'

// 値に名前を付けたいとき
// 外からつかうときにclientStatusValueをimportして使う
export const clientStatusValue = {
    init: 0,
    active: 1,
} as const;
export type ClientStatus = typeof clientStatusValue[keyof typeof clientStatusValue];

null vs undefined

nullも使用を許可する
DBにもnullが使われているため

namespaceを使うタイミング

exportが複数あるとき
namespaceの命名はファイル名にする

使用する

export namespace ModuleA {
  export type TypeA = 'abc' | 'def'
  export type TypeB = 'xyz' | '123'
}

使用しない

type TypeA = 'abc' | 'def'
export default TypeA

ディレクトリ構成

-- src
 |-- api
   |-- controller
   |-- response
   |-- route
   |-- types

api

expressのコードが入る
apiに関するクラス

route

-- route
 |-- public
   |-- healthCheckPublicRoute.ts
 |-- client
 |-- talent

public/client/talentとそれぞれ利用者に向けた単位で切っている
publicの場合は /
clientの場合は /client/xx
talentの場合は /talent/xx というパス設計になっている

/health_checkの場合ファイル名は
healthCheckPublicRoute.tsとなる

controller

-- controller
 |-- public
   |-- healthCheckPublicController.ts
 |-- client
 |-- talent

routeディレクトリと完全に一対一となっている
classとして定義し、staticメソッドのみでインスタンス化はしない

controllerのメソッド名は
GET /health_checkであれば、get
POST /health_checkであれば、postとCRUDに名前を合わせる
/health_check/searchという名前であれば、searchというメソッドを生やす

命名規則は
healthCheckPublicControllerとする

response

レスポンスの型を定義している

-- response
 |-- models
  |-- projectGenreApiResponseModel.ts
 |-- public
  |-- projectGenrePublicApiResponse.ts
 |-- BaseResponse.ts

modelsはレスポンスの共通の型を置く
publicフォルダはそのAPIごとのレスポンス型を定義するエンドポイントにつき一つ作る

application

Applicationレイヤーに該当
Domainを使って、振る舞いを達成するようなコードを書く
Domainに依存することはできるが、Othersレイヤーに依存することはできないので、infra層にアクセスするときは、repositoryをコンストラクター経由で受け取って利用する

-- application
 |-- usecase
  |-- job
    |-- jobFetchAllUseCase.ts

ユースケースごとに実装するので、一つのクラスにつき、publicのメソッドは一つしかはやさない

cli

cliで実行される処理
cronなどの処理が書かれている

ファイルの命名規則は、xxCli.ts

consts

定数ファイル
グローバルな型もこの中に描かれる
ファイル命名規則はxxConsts.ts

namespaceで必ず囲う

export namespace ClientConsts {
    export const clientStatusValue = {
        init: 0,
        active: 1,
    } as const;
    export type ClientStatus = typeof clientStatusValue[keyof typeof clientStatusValue];
}

converter

変換器
特定の型から特定の型に変換する処理を行う
変換したい型をファイル名、クラス名とする

domain

-- domain
 |-- repository
 |-- model
 |-- type

modelはドメインをクラスとして表す
repositoryはドメインの振る舞いをinterfaceで定義する

exception

Exceptionを表すクラス
グローバルに使うことができる

infra

infrastructure層

-- infra
 |-- db
   |-- datasource
   |-- entities
 |-- elasticSearch
   |-- datasource
   |-- entities
 |-- nicoAi

命名規則
dbのentitiesはXXDbEntity.ts, datasourceはXXDbDatasourceとする

lib

外部ライブラリをラップしたり、汎用性の高い処理を書くクラス
この部分の基準は、他のプロダクトにうつしても利用できるかどうかという基準で考える

repository

domain/repositoryの処理を具体的にした部分

最後に

この設計で実際にプロダクトを作っていく中でいくつかの悩みポイントが出てきた

  • DBのテーブルすべてをかならずしもDomainにする必要はないのではないか
  • その場合はInfra層をcliやapi側で直接呼び出すことになるが、どっちで呼び出すか迷う
  • cronで、DBのデータをcsvに変換してアップロードするみたいな処理をusecaseで定義するか微妙
    などなど悩ましい部分はありますが、今後作っていくなかで徐々にアップデートしていきたいと思います。

Another worksでは一緒に働ける仲間を探しています

24
18
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
24
18