JavaScript
CleanArchitecture
0

JavaScriptでクリーンアーキテクチャはどうすればいいのか(DataStore編)

JavaScriptでクリーンアーキテクチャはどうすればいいのか(DataStore編)

by Gatz
1 / 8

DataStoreとは?

  • DBやExternal InterfacesへのGateway(の様子)
    • DBやExternal interfacesとはつまり、データの入手先・保存先のこと
    • Gateway? どういうことだってばよ?
  • gateway.q(from:"勤務実績")と唱えるだけで、必要な結果が得られる
    • 勤務実績がどのDBにあってもよい
    • DBMS、WebSite、ファイル、、、全てOK!
  • データへのアクセス手段をドライバとして提供する必要がありますな

複数のデータソースを一個に

  • 役割名はGateway(入出力どちらもあるから)
  • DBの足りない値を補完(表示順序値とか)できる
  • RDBのview張るようなもん?
COMPANY.tables = {
  "勤務実績": "labor_cost",
  "所属部門": "departmentList",
  "従業員": "users"
}

COMPANY.config = {
  tableRowType: Array,
  datasources: [
    {
      name: "JSO_DB",   // 同じ識別子で同一DBとみなす                                                                       
      type: JSOStore,   // window[TABLENAME]に実体が格納されているDB                                                        
      tableType: Array, // TableのRow構成が配列の場合                                                                       
      tableList: {      // createするときにちょっと余計なことができる
        "labor_cost" : function(tablenames){
          const sheetnames = tablenames.map((tablename)=>{return tablename.replace("labor_cost","")})
          return new Table({name: "labor_cost"})
            ._assign("thead", window[tablenames[0]].thead)
            ._assign("tbody", [])
            ._assign("sheetnames", sheetnames)
            ._assign("sheets", sheetnames.inject({}, (prev, sheetname, i)=>{
              return prev._assign(sheetname, window[tablenames[i]].tbody)
            }))
        },
        "labor_unit": null,
        "projects": null,
        "category0": null,
        "category00": null,
        "jobs": null
      }
    },{
      name: "JSO_DB",    // 既出なら、既出DataStoreオブジェクトがthisにセットされる                                         
      type: InlineStore, // config中に実体が格納されているDB                                                                
      tableType: Object, // TableのRow構成が連想配列の場合                                                                  
      tableList : {
        "departmentList": [{"所属":"A課","表示順":0},
                           {"所属":"B課","表示順":1},
                           {"所属":"C部","表示順":2}],
        "laborTypeList": [{"勤務形態":"社員", "表示順":0},
                          {"勤務形態":"パート","表示順":1}
      }
    }
  ]
}


DataStoreの実装は?使うときは?

実装

  • DataStore#constructor(config)
    • config: DB接続に必要な情報
  • DataStore#q(jsQUERY): Table
    • jsQUERY: JavaScriptで記述したSQL
    • Table: Clean ArchitechtureとしてのEntityデータクラス
      • 当初DomainModelとしていたがEntityへ
  • サブクラスとしてJSOStoreとかInlineStoreとか
  • DataStores#create(COMPANY): Object(valueはDataStore)
    • COMPANY.config情報を使って、DataStoreの具象クラスを作るファクトリメソッド
    • 戻り値: COMPANY.tablesのキーをキーとして、DataStoreサブクラスのインスタンスを値としてもつ
    • DataStores.q({from:"勤務実績"})としたら、JSOStore.q({from: "勤務実績"})を呼び出してくれる
  • 確かに、view張るのできるな。

使うとき

  const repository = new LaborRepository(COMPANY);

class LaborRepository{
  constructor(company){
    this.COMPANY = company
    this.stores = DataStores.create(company)
  }
}

class DataStores {
  constructor(stores){
    this.stores = stores
  }
  static create(company){
    return new DataStores(company.config.datasources.map(function(config){
      return new config.type(config)
    }))
  }
  list(tabname){
    if (this._list == null){
      this._list = this.stores.inject({}, (prev, db)=>{
        db.list().forEach((tname)=>{
          prev._assign(tname, db)
        })
        return prev;
      })
    }
    return tabname == null ? this._list.keys() : this._list[tabname]; //ここ気持ち悪い
  }
  q(jsQUERY){
    return this.list(jsQUERY.from).q(jsQUERY);
  }
}

class DataStore {
  constructor(config){
    this.config  = config
  }
  list(){
    return this.tables.keys();
  }
  from(tabname){
    return this.tables[tabname]
  }
}

class JSOStore extends DataStore {
  constructor(config){
    super(config)
    // ここでwindow[tabname]に格納されたテーブルをプログラム中で参照できるようにする                                        
    this.tables = this.config.tableList.fold({}, (prev, tabname, cb)=>{
      if (cb == null)
        return prev._assign(tabname, new Table(window[tabname]))
      return prev._assign(tabname, cb.call(this, window.keys().filter((key)=>{return key.match(tabname)})));
    })
  }
}
class InlineStore extends DataStore {
  constructor(config){
    super(config)
    this.tables = this.config.tableList
  }
}

DataStoreとRepository

  • RepositoryがDataStoreを所有し、Usecaseから隠蔽
    • DataSourceの実体をUsecaseは知らなくてOK
  • config与えたらそれでおしまいにできる
    • test用のconfigとか差し替えればいいよね
  • COMPANY.configも良い
    • COMPANYは業務上の情報があるが、COMPANY.configは外部のドライバ情報
    • DBやExternal Interfacesには業務情報は不要
  • 綺麗に役割分担できててすごくね?

DataStoreとRepository


DataStoreとRepositoryの違い

  • DataStoreは抽象化されたデータの塊=Entity
    • CRUID程度できればそれで良い
  • Repositoryは人間にわかる意味を持ったデータの塊=DomainModel
    • EntityとDomainModelの違いは「意味」の違い
    • 同じTable構造を取っていても、入ってる値は全然違う
    • データ構造を変換するのがRepositoryの役割と言うよりも、
    • データに意味を付与するために、データ構造を変換するのでは?
  • Usecaseは中身のデータ構造とか知りたくないからね
    • 知る必要がないともいう
  • O/Rマッパで、DataStoreから直接DomainModelがえられれば
    • なるほどもっと話が簡単になるわけだ

まとめ

  • DataStoreは抽象化されたデータの塊
    • 役割: 複数のデータソースを単一に見せる
    • 効果:
      • データがメモリにあるかネットの先か
      • 本番かテスト環境か
      • 与えるconfig次第で容易に交換可能
  • Repositoryの役割も明確になる
    • 役割: データの器Entityに人間がわかる意味を付与する
    • Usecaseで「今年一年勤務した従業員」と書かれてあれば、
      • 「」が業務上の関心つまりDomainModel
    • Repositoryは「勤務実績一覧から、今年一年ぶんを抜き出し、さらに氏名を重複なく列挙したもの」を提供する
      • 「」がビジネスロジックつまりRepositoryが提供するAPI
      • ビジネスロジックはデータに意味を与えるデータ構造変換のことだったのか!
  • 戻り値がインターフェースっていうのは、DataStoreとして抽象化したという話なのかしら
    • そういうこととして理解しよう

DomainModel編へ続く

全記事