9
24

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-09-23

#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編へ続く

全記事

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?