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

初めまして、株式会社Another works CTOの塩原です。
弊社では、[複業クラウド](https://cl.aw-anotherworks.com/)というサービスを運営しており、今回はそのサービスのAPI側の設計を公開したいと思います。

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

![スクリーンショット 2021-11-13 11.33.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114212/4b623b57-b3ba-8a8b-a8a3-401220fa4cab.png)

# 依存関係
![スクリーンショット 2021-11-18 18.35.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114212/65e84b71-1fac-bb63-15c5-83463a3487d2.png)

# ディレクトリ構成
![スクリーンショット 2021-11-13 20.46.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114212/56013a5f-3501-5d26-a8f1-5f4036d5a71f.png)



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

```js
'@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の書き方

```ts
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の命名はファイル名にする

使用する

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

使用しない

```ts
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で必ず囲う

```ts
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では一緒に働ける仲間を探しています
https://www.wantedly.com/projects/729427

https://www.wantedly.com/projects/617507

https://www.wantedly.com/projects/683778

https://www.wantedly.com/projects/691559