前提のアーキテクチャ
Presentation
表示を担う
Repository
外部との接続を担う。
Usecase(Application)
Repository層、Store層とのコネクション。
Repository層からとってきたデータをStoreに保存する。
ビジネスユースケースを記述。
Entity
モデル。(DDDに置けるドメインモデリング)
Query
Store層から取り出したデータを編集して取り出す。
開発言語,フレームワーク
Typescript, Angular
困ったこと・対策
Presenation編
Componentしていくが、再利用性がないものもComponent化していくのか迷った。
例
- 「Google検索」、「I'm Feeling Lucky」と書かれているButtonは再利用性があるので、Component化しよう。
- Googleのロゴはコンポーネント化するのか迷う
対策
一つの画面がもつComponentと再利用性があるComponentに分けた。
├── modules
├── GoogleLogoComponent
└── pages
├── GoogleLogoComponent
Entity編
Entity、値オブジェクトを記述するとき、巷で流行っているような記述はフロントエンドではコスパが悪い、、?
export class ValueObject {
private _value: string;
constructor(value: string) {
this._value = value;
}
get getValue(): string {
return this._value;
}
set setValue(value: string) {
this._value = value;
}
}
上記のように、一つのオブジェクトを表現するのにもこの記述がかかる。
そのくせ、基本的にフロントエンドでは一つのドメインオブジェクトにおけるドメインロジックが少ないことが多い(扱うもので異なる)
対策
記述をinterfaceにとどめた。Validationなどの記述は同じファイルにfunctionとして記述。
export interface ValueObject {
value: string;
}
export function isExist(value: ValueObject): boolean {
return this.value ? true : false;
}
値オブジェクトに関してはカプセルかする必要あるのか
例
UserIdのような値オブジェクトは、storeなどのライブラリと相性が悪い。
(userId: string | number
を想定してることが多い)
export interface UserId {
value: string;
}
対策
uidのような値オブジェクトはライブラリとの相性の悪さを勘案して、typeでstringと同列な型とした。
これによって、引数を表す時などに、可視化はできるようになった。
export type UserId = string;
Query
query処理が煩雑になりがち。
ただapiからfetchしたJsonDataを使ってqueryを処理するのは難しくなりがち。
対策
正規化することで、RDBのようにquery処理を記述することができる。
参考
https://www.slideshare.net/ayatas0623/reduxstate-129830690
Store
storeの分割単位はPage単位?ドメインオブジェクト単位???
一つのpageでは、複数のドメインオブジェクトを扱うことが多い。したがって、ドメインオブジェクト単位でのStoreの設計だと、扱うStoreも多い。対してPage単位でStoreを構成することで、Storeの扱いがPageのなかの限定的になるが、扱いやすくなる。
対策
現状ドメインオブジェクト単位にしています。ただ、これといった明確な理由はないのでベストプラクティスを模索中です、。
Usecase
データ取得するのはどこの責務?
データ取得する場所は使っているフレームワーク、ライブラリなどで結構別れたりする。例えばVue.jsの場合はVuexをStoreで使ったりすることが多いが、Vuexのアクションでは非同期処理が勧められている。
つまり、Presentationで発火し、ActionをDispatch。そこで非同期でAPIを発火させ、ミューテーションに値を送る形になっている。ただ、ライブラリで勧められているならまだしも、StoreでRepositoryを扱うのはどうかと疑問に思った。
対策
InitializeUsecaseとして、Usecase層で処理を行う。
export class InitializeUsecase {
constructor() {}
exec() {
const repository = new EntityRepository()
const entity = repository.fetch()
const store = new Store()
store.dispatch(entity)
}
}
最後に
自分がフロントエンドDDDをする際に困ったところをかいつまんでまとめました。誰かの参考になりますと幸いです。