dackdive's blog

新米webエンジニアによる技術ブログ。JavaScript(React), Salesforce, Python など

follow us in feedly

MacにMongoDB(3.4.7)をHomebrewでインストールする

メモ。
3 年前にも書いたが、今これを読みながらインストールしたらいろいろ情報が古くなっていたので改めて。

環境

  • Mac OS X Yosemite 10.10.5
  • Homebrew 1.3.1
  • MongoDB 3.4.7


MongoDB の特徴

↑の記事を参照。


インストール手順

基本的には公式ドキュメントに従ってインストールすれば OK。
Install MongoDB Community Edition on OS X — MongoDB Manual 3.4

1. Homebrew をアップデートする
$ brew update
2. MongoDB をインストール
$ brew install mongodb

でインストールされる。

3. プロセスの起動

先ほどのインストールの最後に

To have launchd start mongodb now and restart at login:
  brew services start mongodb
Or, if you don't want/need a background service you can just run:
  mongod --config /usr/local/etc/mongod.conf

というメッセージが表示される。
Homebrew Services という機能を使って、再起動後もプロセスが自動起動するように設定するには

$ brew services start mongodb

を実行する。

そうではなく、一回限りの起動を行う場合は

$ mongod --config /usr/local/etc/mongod.conf

を実行する。

4. mongo コマンドの実行

3 でプロセスを起動した状態で

$ mongo

を実行する。

$ mongo
MongoDB shell version v3.4.7
connecting to: mongodb://127.0.0.1:27017
MongoDB server version: 3.4.7
Server has startup warnings:
2017-08-19T23:23:55.973+0900 I CONTROL  [initandlisten]
2017-08-19T23:23:55.973+0900 I CONTROL  [initandlisten] ** WARNING: Access control is not enabled for the database.
2017-08-19T23:23:55.973+0900 I CONTROL  [initandlisten] **          Read and write access to data and configuration is unrestricted.
2017-08-19T23:23:55.973+0900 I CONTROL  [initandlisten]
> show dbs # データベース一覧を表示
admin  0.000GB
local  0.000GB


注意

ちょっとハマった点について。

config ファイルの場所

↑のコマンドからもわかるように、/usr/local/etc/mongod.conf という設定ファイルが作成されている。
中身はこんな感じ。

systemLog:
  destination: file
  path: /usr/local/var/log/mongodb/mongo.log
  logAppend: true
storage:
  dbPath: /usr/local/var/mongodb
net:
  bindIp: 127.0.0.1

ログ・ファイルや DB ファイルへのパスが指定されていることがわかる。

注意点として、 mongod コマンド実行時にデフォルトでこの設定ファイルが読み込まれるわけではない

DB ファイルのパス

設定ファイルに /usr/local/var/mongodb と書かれているのでこれがデフォルトかと思ったら違うらしい。

/data/db

というパスのようだ。

参考:https://docs.mongodb.com/manual/reference/configuration-options/#storage.dbPath

また、このディレクトリは自動的に作られるわけではないので、mongod コマンドを --config オプションなしで実行する場合は事前に

$ mkdir -p /data/db

ディレクトリを作成しておく必要がある。 Read/Write 権限も必要。
これをやらずに mongod コマンドだけを実行すると

2017-08-19T23:43:14.258+0900 I CONTROL  [initandlisten] MongoDB starting : pid=47588 port=27017 dbpath=/data/db 64-bit host=yama.local
2017-08-19T23:43:14.259+0900 I CONTROL  [initandlisten] db version v3.4.7
2017-08-19T23:43:14.259+0900 I CONTROL  [initandlisten] git version: cf38c1b8a0a8dca4a11737581beafef4fe120bcd
2017-08-19T23:43:14.259+0900 I CONTROL  [initandlisten] OpenSSL version: OpenSSL 1.0.2l  25 May 2017
2017-08-19T23:43:14.259+0900 I CONTROL  [initandlisten] allocator: system
2017-08-19T23:43:14.259+0900 I CONTROL  [initandlisten] modules: none
2017-08-19T23:43:14.259+0900 I CONTROL  [initandlisten] build environment:
2017-08-19T23:43:14.259+0900 I CONTROL  [initandlisten]     distarch: x86_64
2017-08-19T23:43:14.259+0900 I CONTROL  [initandlisten]     target_arch: x86_64
2017-08-19T23:43:14.259+0900 I CONTROL  [initandlisten] options: {}
2017-08-19T23:43:14.263+0900 I STORAGE  [initandlisten] exception in initAndListen: 29 Data directory /data/db not found., terminating
2017-08-19T23:43:14.263+0900 I NETWORK  [initandlisten] shutdown: going to close listening sockets...
2017-08-19T23:43:14.263+0900 I NETWORK  [initandlisten] shutdown: going to flush diaglog...
2017-08-19T23:43:14.264+0900 I CONTROL  [initandlisten] now exiting
2017-08-19T23:43:14.264+0900 I CONTROL  [initandlisten] shutting down with code:100

というようにエラーで落ちる。

brew services start mongodb では config ファイルは読み込まれているのか?

自動起動の方のコマンドを使った場合、/data/db がなくても特にエラーにならない。ということで config ファイルが読み込まれているのだろうと推測できるが、

$ brew services list
Name    Status  User     Plist
mongodb started yamazaki /Users/yamazaki/Library/LaunchAgents/homebrew.mxcl.mongodb.plist
redis   stopped

で homebrew.mxcl.mongodb.plist を見てみると

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>homebrew.mxcl.mongodb</string>
  <key>ProgramArguments</key>
  <array>
    <string>/usr/local/opt/mongodb/bin/mongod</string>
    <string>--config</string>
    <string>/usr/local/etc/mongod.conf</string>
  </array>
  <key>RunAtLoad</key>
  <true/>
  <key>KeepAlive</key>
  <false/>
  <key>WorkingDirectory</key>
  <string>/usr/local</string>
  <key>StandardErrorPath</key>
  <string>/usr/local/var/log/mongodb/output.log</string>
  <key>StandardOutPath</key>
  <string>/usr/local/var/log/mongodb/output.log</string>
  <key>HardResourceLimits</key>
  <dict>
    <key>NumberOfFiles</key>
    <integer>4096</integer>
  </dict>
  <key>SoftResourceLimits</key>
  <dict>
    <key>NumberOfFiles</key>
    <integer>4096</integer>
  </dict>
</dict>
</plist>

plist ファイルの読み方はわからないが、 ProgramArguments の後にそれらしい記述が見られるので読み込まれているのだと理解した。

React Hot Loader 3 と webpack 3 でHot Module Replacement(react-hmreからの移行)

経緯

dackdive.hateblo.jp

これまで webpack-dev-server の Hot Module Replacement を React/Redux でも有効にするために babel-preset-react-hmre を使っていたが、いつの間にか GitHubリポジトリがなくなっていて deprecated ぽいので別のプラグインを探していた。

公式ドキュメント を読む限り React Hot Loader が一番有力のようなので、そちらへ移行する。

サンプルコード(GitHub

先に、今回の移行後のコードが確認できる GitHub リポジトリを載せておく。

PR は #4, #6


設定手順

ここを読む。
Getting Started · React Hot Loader

※以下、(Step X) は上記ドキュメント中の Step と対応している

React Hot Loader のインストール

2017/08/09 時点で v3 はベータ版のようなので、 @next をつけてインストールする。

$ npm install --save-dev react-hot-loader@next


webpack 側の設定をする (Step1)

ここについては webpack-dev-server をどのように利用しているかによってやることが異なる。

(Option 1) webpack-dev-server をスタンドアロンなサーバーとしてそのまま利用している場合

これまで通り --hot オプションつきで実行すれば OK。

// package.json
"scripts": {
    "start": "webpack-dev-server --hot"
  },
(Option 2) webpack-dev-server をカスタマイズして使っている場合

未検証。ドキュメントの通りにやればいいのでは。

(Option 3) の webpackDevMiddleware, webpackHotMiddleware として Express に組み込んで使っている場合

自分の場合はこれ。
参考:webpack-dev-serverをExpressに組み込んで使う(webpack-dev-middleware, webpack-hot-middleware) - dackdive’s blog

ドキュメントは TODO になっていたが、以下ができていれば特に設定変更は不要だった。

  • webpack.config.js の entry'webpack-hot-middleware/client' を追加する
  • webpack.config.js の pluginsnew webpack.HotModuleReplacementPlugin() を追加する
  • Express 側で以下のように middleware を組み込む
import express from 'express';
import path from 'path';

const app = express();
const port = process.env.PORT || 8080;

if (process.env.NODE_ENV !== 'production') {
  /* eslint-disable global-require, import/no-extraneous-dependencies */
  const webpack = require('webpack');
  const webpackDevMiddleware = require('webpack-dev-middleware');
  const webpackHotMiddleware = require('webpack-hot-middleware');
  const config = require('./webpack.config.dev.babel').default;
  const compiler = webpack(config);

  app.use(webpackHotMiddleware(compiler));
  app.use(webpackDevMiddleware(compiler, {
    noInfo: true,
    publicPath: config.output.publicPath,
  }));
}

// Serve static files
app.use(express.static(path.join(__dirname, 'public')));

console.log(`Served: http://localhost:${port}`);
app.listen(port, (err) => {
  if (err) {
    console.error(err);
  } else {
    console.info(`==> 🌎  Listening on port ${port}. Open up http://localhost:${port}/ in your browser.`);
  }
});

詳細は リポジトリ を参照。


Babel または webpack に react-hot-loader を追加する (Step 3 の 2)

2 通りの方法がある。
.babelrc

{
  "plugins": [
    "react-hot-loader/babel"
  ]
}

を追加するか、または webpack.config.js 側に

    module: {
        loaders: [{
            test: /\.js$/,
            loaders: ['react-hot-loader/webpack', 'babel'],
            include: path.join(__dirname, 'src')
        }]
    }

を追加する。

いずれも、 NODE_ENV === development のときだけ、とは書かれていなかった。


アプリのエントリーポイントに react-hot-loader/patch を追加する (Step 3 の 3)

こちらも 2 通りの方法がある。
アプリのルートコンポーネントとなる JS ファイルの1行目に

import 'react-hot-loader/patch';

を追加するか、または webpack.config.js の entry

  entry: [
    'react-hot-loader/patch', // これを追加
    './scripts/index' // アプリのエントリーポイント
  ]

を追加する。

自分は前者の方にした。


ルートコンポーネントに HMR の仕組みを入れる (Step 2)

最後に、アプリのルートコンポーネントに HMR の仕組みを入れる。

https://github.com/zaki-yama/redux-express-template/blob/master/public/index.js

import 'react-hot-loader/patch';
import React from 'react';
import { Provider } from 'react-redux';
import { render } from 'react-dom';
import { AppContainer } from 'react-hot-loader';

import configureStore from './store/configureStore';
import App from './containers/App';

import './styles/index.scss';

const store = configureStore();

const rootEl = document.getElementById('root');

render(
  <AppContainer>
    <Provider store={store}>
      <App />
    </Provider>
  </AppContainer>,
  rootEl,
);

if (module.hot) {
  module.hot.accept('./containers/App', () => {
    render(
      <AppContainer>
        <Provider store={store}>
          <App />
        </Provider>
      </AppContainer>,
      rootEl,
    );
  });
}

babel-preset-react-hmre のときはこのあたり自前で実装する必要はほぼなかったんだけど、React Hot Loader だと if (module.hot) { ... } 部分の処理が必要になるらしい。

<Provider store={store}>
  <App />
</Provider>

部分は Redux を使っているときのお決まりの書き方だが、これをさらに React Hot Loader が提供している <AppContainer /> というコンポーネントでラップする。


余談:ハマったポイント

ドキュメントでは

const render = Component => {
  ReactDOM.render(
    <AppContainer>
      <Component />
    </AppContainer>,
    document.getElementById('root')
  );
}

render(RootContainer);

if (module.hot) {
  module.hot.accept('./containers/rootContainer', () => { render(RootContainer) });
}

というように記載されていたので、最初は以下のようにした。

// index.js
import 'react-hot-loader/patch';
import React from 'react';
import { render } from 'react-dom';
import { AppContainer } from 'react-hot-loader';

import Root from './containers/Root';

const renderApp = (Component) => {
  render(
    <AppContainer>
      <Component />
    </AppContainer>,
    document.getElementById('root'),
  );
};

renderApp(Root);

if (module.hot) {
  module.hot.accept('./containers/Root', () => {
    renderApp(Root);
  });
}
// ./containers/Root.js
import React from 'react';
import { Provider } from 'react-redux';

import configureStore from '../store/configureStore';
import App from './App';

import '../styles/index.scss';

const store = configureStore();

export default class Root extends React.Component {
  render() {
    return (
      <Provider store={store}>
        <App />
      </Provider>
    );
  }
}

が、Redux の store が絡むとうまくいかないのか、上記のようにすると HMR 時にこのようなエラーが発生した。

does not support changing store on the fly. It is most likely that you see this error because you updated to Redux 2.x and React Redux 2.x which no longer hot reload reducers automatically. See https://github.com/reactjs/react-redux/releases/tag/v2.0.0 for the migration instructions.

調べてみたところ
Hot reload not working · Issue #502 · reactjs/react-redux
こちらの Issue が見つかり、さらにそこから redux-devtools の TodoMVC の example更新されていた のを見つけたので
example のコードを確認し、解決した。

また React Hot Loader の Starter Kit で紹介されている大量のリポジトリの中から

なんかを読むと、(src/client/index.js の部分)

import 'react-hot-loader/patch';
import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import { Provider } from 'react-redux';
import { AppContainer } from 'react-hot-loader';

import configureStore from './store/configureStore';
import App from './containers/App';

import './styles/index.scss';

const store = configureStore();

const rootEl = document.getElementById('root');
const renderApp = () => {
  render(
    <AppContainer>
      <Provider store={store}>
        <App />
      </Provider>
    </AppContainer>,
    rootEl,
  );
};

renderApp();

if (module.hot) {
  module.hot.accept('./containers/App', () => {
    setImmediate(() => {
      unmountComponentAtNode(rootEl);
      renderApp();
    });
  });
}

というように一旦アンマウントしてから再描画でもうまくいくらしい。本当にこれが正しいのかは不明。

React 16で導入されたError Boundaryについて

React 16 Beta がリリースされました。
Error Boundary という概念が導入されたそうなので公式ブログをざっくり読んでみます。

はじめに:React 15 までの問題

React 15 まではコンポーネントで発生したエラーをうまくハンドリングしたり、そこからリカバリするためのしくみが提供されてませんでした。
結果として

  • コンポーネントのどこかで JavaScript エラーが発生する
  • catch していない場合、知らないうちに React の内部状態(internal state)にも悪影響を及ぼす
  • 結果として開発者コンソール上はぱっと見原因がよくわからないエラーが発生する

みたいなことが起きてました。

(参考)
TypeError: Cannot read property ‘_currentElement’ of null · Issue #4026 · facebook/react
Error: performUpdateIfNecessary: Unexpected batch number … · Issue #6895 · facebook/react
Cannot read property ‘getHostNode’ of null · Issue #8579 · facebook/react


Error Boundary の導入

デモ:https://codepen.io/gaearon/pen/wqvxGa

Error Boundary は コンポーネントツリーで発生したエラーをハンドリングする ための React コンポーネントです。
ブログに記載されているサンプルコードを見てみます。

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  componentDidCatch(error, info) {
    // Display fallback UI
    this.setState({ hasError: true });
    // You can also log the error to an error reporting service
    logErrorToMyService(error, info);
  }

  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return <h1>Something went wrong.</h1>;
    }
    return this.props.children;
  }
}

注目すべきは componentDidCatch(error, info) という新しいライフサイクルメソッドで、これが実装されたコンポーネントのことを Error Boundary と呼ぶようです。

使うときは

<ErrorBoundary>
  <AddTodo />
  <VisibleTodoList />
  <FilterLinkList />
</ErrorBoundary>

のように、エラーをハンドリングしたいコンポーネントツリーをラップするようにして通常のコンポーネントと同じように記述します。

このようにすると、<ErrorBoundary> 以下のコンポーネントツリーで JavaScript エラーが発生したときに componentDidCatch() がエラーを捕捉してくれます。
レンダリング時だけでなく、コンストラクタやライフサイクルメソッド内で発生したエラーも捕捉できます)
try/catchcatch 句と同じですね。

そのため、あるコンポーネントツリーでエラーが発生したときもクラッシュせず
ログを送信したり、エラーメッセージ用のコンポーネントに差し替えるといった対応が可能になります。

注意

Error Boundary が捕捉するのは コンポーネントツリー内で発生したエラーのみで、自コンポーネント内で発生したエラーは捕捉できません。
つまり Error Boundary コンポーネント内のライフサイクルメソッドや render() の書き方をミスってエラーが発生した場合、それは componentDidCatch() で捕捉できません。


componentDidCatch(error, info) の引数の中身

デモ を確認した限り、

  • 第一引数の error は Error オブジェクト( messagestack をプロパティとして持つ)
  • 第二引数の infocomponentStack というプロパティを持つオブジェクト

のようです。
また componentStack には

in BuggyCounter (created by App)
in ErrorBoundary (created by App)
in div (created by App)
in App

のような情報が入っています。Component Stack Trace と呼んでいるようです。(後述)


Error Boundary はコンポーネントツリーのどこに配置すべきか

Error Boundary は複数配置することも可能なので、ツリーのどこに Boundary を設けるかは要件次第です。
アプリケーションのルートに「不明なエラー(Something went wrong)」というメッセージを表示する Boundary を1個だけ置いてもいいですし、
画面の一部分が壊れても他の部分は利用可能な状態を維持したいのであれば、その境界ごとに複数の Boundary を用意する可能性も考えられます。

例として、Facebook Messenger はサイドバーや info panel、チャット部分などはそれぞれ Error Boundary でラップしてるそうです。


Uncaught Error 時の挙動の変更

個人的にこれが大きな仕様変更だなと思いました。
React 16 から、どの Error Boundary でも捕捉されないエラー(Uncaught Error)が発生した場合 コンポーネントツリー全体が unmount され、非表示になります。

先ほどのデモを fork して試してみます。
https://codepen.io/zaki-yama/pen/dzYWNM

f:id:dackdive:20170729013405g:plain

一番下に追加したカウンター(インクリメントして 5 になるとエラー)は Error Boundary でラップしていないのですが、ここで発生したエラーのせいで画面全体がクリアされているのがわかります。


React 15 のときの挙動と比較

https://codepen.io/zaki-yama/pen/RZWVpK

f:id:dackdive:20170728125201g:plain

React 15 のときはコンソールでエラーが発生しているものの、画面自体はそのまま残っていることがわかります。


なぜこのような仕様に変更されたのか

コンポーネントツリーが unmount されて画面全体が操作不能になることより、エラーによって UI や内部状態がおかしくなったことに気づかず中途半端に動作し続けることの方がまずい、との結論に達したようです。
たとえば、商品の支払い画面の UI で実際に表示されている金額と内部で保持している金額が乖離したまま決済を実行されるリスクがあるのは危険ですね。


Component Stack Traces

React 16 ではレンダリング中に発生したエラーは必ずコンソールに出力されます。(in development とあるので NODE_ENV === development の時のみ?)
ログにはエラーメッセージと JavaScript の stack trace に加え、コンポーネントの stack trace も出力されるようになります。

f:id:dackdive:20170729020849p:plain

これにより、コンポーネントツリーのどのコンポーネントでエラーが発生したのか追いやすくなります。


さらにファイル名と行番号も出力する

component stack trace にファイル名と行番号を出力することもできます。create-react-app の場合はデフォルトで有効になっています。
creacte-react-app を使わない場合、babel-plugin-transform-react-jsx-source という Babel プラグインを追加する必要があります。
また追加した際は production build 時には無効化する必要があります。


感想

これまで、アプリで発生したエラーを React ではどのように処理すべきなのかがいまいちわかっていなかったんですが、Error Boundary の導入によって基本的な実装方法がわかりました。
エラー時に外部の error reporting サービスに送信したり、UI のパーツごとに境界を設けてあるパーツが壊れても他のパーツは機能するように、といった制御はかなりやりやすくなりましたね。

Uncaught Error 時の仕様変更については、予期せぬエラーで画面が真っ白になるという状況は避けたいので、アップデート前に最低限アプリのルートには Boundary を設けておいた方が良さそうです。


React 16 Beta を今すぐ試すには

React 16 beta · Issue #10294 · facebook/react

によると

# Yarn
yarn add react@next react-dom@next

# Npm
npm install --save react@next react-dom@next

でインストールできるそうです。未確認。

[Chrome拡張]chrome.storage.localやchrome.storage.syncでストレージに保存したデータを確認・削除する

メモ。
大昔に作った Chrome 拡張に手を加えようと思い立った。

今回は chrome.storage API を使って設定内容をローカルに保存できるようにしたかったんだけど
開発時に保存したデータを確認したり、一旦削除したりするにはどうすればいいのかわからず調べてみた。

GUI で確認する方法とコンソールで確認する方法を示す。

参考


方法1:Storage Area Explorer という Chrome 拡張を使う

ストレージを確認するための Chrome 拡張があるらしい。

使い方は、インストールした後ストレージの内容を確認したい Chrome 拡張のポップアップやバックグラウンド、オプションページを開く。

ポップアップはアイコン右クリック -> 「ポップアップを検証」から。

f:id:dackdive:20170726190108p:plain

バックグラウンドやオプションページは chrome://extensions から。

f:id:dackdive:20170726190348p:plain

その状態でデベロッパー・ツールを開くと、「Storage Explorer」というタブが追加されているはず。

f:id:dackdive:20170726190517p:plain

さらにその下の「chrome.storage.local」「chrome.storage.sync」というそれぞれのタブから、保存されているデータが確認できる。
「Clear」または行の右の削除ボタンを押すとデータを削除することができる。


方法2:デベロッパー・ツールでコマンドを実行する方法

方法1と同じようにデベロッパー・ツールを開いた後、コンソールで以下を実行する。

// sync または local
chrome.storage.sync.get(null, function (data) { console.info(data) });

こうするとコンソールに保存されているデータが表示される。

f:id:dackdive:20170726190920p:plain

削除したい場合はドキュメントに従い remove(key) または clear() を実行する。

chrome.storage.sync.remove('customFormat', function (data) { console.log('Successfully deleted', data); });

// または全削除の場合
chrome.storage.sync.clear()

IDDD本もくもく読書会メモ#2(第3章 コンテキストマップ)

第1回 に続いて第2回も無事に開催することができました。

※社外からの参加者もお待ちしています(Slack グループ

教材

実践ドメイン駆動設計

実践ドメイン駆動設計

書籍に加え、前回見つけた CodeZine の解説記事

今回は書籍の前に目を通したが、最初に概要を掴んでから書籍に入れるので効果的。今後もこの進め方でいきたい。

第2回で読んだ範囲

第3章を一通り。この章はボリュームが小さくて助かった。


学習メモ

第2章で「境界づけられたコンテキスト」を学んだが、「コンテキストマップ」は複数の「境界づけられたコンテキスト」の関係を俯瞰する図となる。
抽出したコンテキストを線で結び、互いの関係性を整理していくイメージ。

f:id:dackdive:20170717225955p:plain:w320

(図は書籍より引用)

2 つのコンテキスト間にはどちらかが上流(Upstream)でどちらかが下流(Downstream)という関係がある。図中には U または D で書く。

プロジェクトの現状を示す図。こうあって欲しいという将来の図ではない。

コンテキストマップを書く理由

http://codezine.jp/article/detail/9837 より引用。

コンテキストマップを描くことによって、システム間の関係を適切に把握できるメリットがあります。DDDチームは既存システムとの連携方法を把握でき、他チームとのコミュケーションの必要性を判断できるようになります。

 コンテキストマップはアーキテクチャ図というよりも、チーム間のコミュケーション関係を示す図の意味合いが強くなります。コンテキストマップは組織間の問題を見つけ出せる唯一のドキュメントとなるため、プロジェクトの成功に不可欠といわれています。

コンテキストマップの分類

境界づけられたコンテキスト間の関係には、いくつか名前の付いた種類がある。
これは CodeZine で解説されているように、まず大分類として

  1. チーム間の関係を示す「組織パターン」
  2. データとプログラムの連携方法を示す「統合パターン」

に分けて考えると理解しやすい。(書籍では全部いっしょくたにしているが)

  1. チーム間の関係を示す「組織パターン」
    • パートナーシップ
      • 2つのコンテキストを担当するチームが協力的な関係にある
    • 別々の道
      • コンテキスト間で統合を行わない
    • 順応者
      • 上流側が下流側の要求に応える必要がない
      • 例:TwitterGitHub などの API を使った場合
    • 顧客/供給者
      • 上流のチームが成功するかどうかが下流の結果に左右されうるという場合、上流(供給者)は下流(顧客)のニーズに対応する必要がある
      • 例:モバイルアプリ開発における、API 開発チーム(上流)とアプリ開発チーム(下流
  2. データとプログラムの連携方法を示す「統合パターン」
    • 共有カーネル
      • 複数ドメインにおいて共有が必要な部分に、共通で使用するドメインモデルを構築してソースコードレベルで共有する
      • 共有カーネルに変更が必要な場合は他のチームの承認が必要になるため、この部分は極力小さくする
    • 巨大な泥団子
      • (既存システムなど)大規模で複雑なものを、そのまま(適切なモデルに分割せず)大きな塊として捉えること
    • 公開ホストサービス(OHS:Open Host Service)
    • 公表された言語(PL:Published Language)
      • 2 つの境界づけられたコンテキスト内にあるモデル同士で変換するための、共通の言語
      • OHS と組み合わせて使うことが一般的
      • 例:JSON
    • 腐敗防止層(ACL:Anti Corruption Layer)
      • 下流側が上流側の機能を自コンテキストのドメインモデルに変換するレイヤ
      • 上流側と協力関係が築けなかった場合に、上流側に振り回されないように設ける変換層
コンテキストの分析と統合ポイント

境界づけられたコンテキストが適切に分割されているかを分析し、複数の概念が 1 つのコンテキストの中に混じっていた場合は Brandolini の記法では三角の警告アイコンを記載する。

f:id:dackdive:20170717235155p:plain:w320

(図は書籍より引用)


ディスカッションメモ

  • OHS と PL のどちらか一方だけしか使われないことってあるんだろうか

→ あんまりなさそう

  • 競合ポイントはどうやって見つけるのか

書籍に出てきた図が(ひょうたん型になっていて)恣意的な感じが…

  • リモートモデル、ローカルモデルとかのくだりがよくわからない

自立性を確保するには、依存するオブジェクトの状態をローカルシステム側に保持しておけばいい。依存するオブジェクト全体をキャッシュしておけばいいと考える人もいるかもしれない。しかし、DDDでは、この考え方は一般的ではない。その代わりに、ローカルのドメインオブジェクトを作って外部のモデルをそれに変換し、ローカルのモデルに必要な最小限の状態だけを保持する。

→ たとえば、GitHub API を使って Issue 管理アプリを作るとき、

  • API で Issue を GET した結果のプロパティをアプリ内で全部保持するわけはなく、実際には必要なものだけ保持するはず
  • API に仕様変更があったときにも影響を最小限にするため、レスポンスをそのまま使うのではなく何らかのオブジェクトに変換して使うはず(ACL にもなる)

というわけで、API のレスポンスを加工してアプリ内で使うためのモデルに変換する、というのはここに記載されたことそのものズバリな気がする。


次回

7/21(金)19:00 頃からやります。


資料

見つけたものをどんどん追加していきます。

"Redux Architecture Guidelines"を読んだ

こちらの記事を読んで。React&Redux でアプリを作る上で基本的だが重要なことが書かれていたので、後で見返すためにメモ。

なお本文は原文記事の完全な翻訳ではなくポイントだけかいつまんで自分の考えも交えたものです。
また、まとめさせていただく上で著者の許諾を得ています。
(拙い英語でおそるおそる訪ねたら流暢な日本語で快諾いただいてびっくり)


State

state の形を計画(設計)する(Plan your state shape

state オブジェクトの構造をきちんと設計することは非常に重要。
こういった構造にすればいい!という唯一解はないが、設計の手助けとなるチェックリスト(質問リスト)がこちら。

  • users, accounts, items など、API 経由で取得した複数のリソースか(配列かどうか、の意?)
  • ローディングアイコンを出す/出さないなど、loading state を扱うか
  • 成功またはエラー時の通知 UI を扱うか
  • 一貫性がありかつ予測しやすいか。別のチームメンバーがすぐに理解できるか
  • 必要なデータにアクセスしやすいか。不必要にネストしていないか
  • serializable か。ローカルストレージやデータベースに簡単に保存できるようになっているか
  • state のかわりに URL でアクセスできるプロパティはないか
    • URL でアクセスできる = state に保存する必要はない?
  • 重複したデータはないか


state の過剰なネストは避ける(Avoid nesting state objects

こういった state 構造になっていることがたまにある。

{
  foo: {
    bar: {
      baz: {
        qux: ...
      }
    }
  }
}

リレーショナルデータをそのまま state に突っ込むとこういった深いネスト構造になりがちだが、深いネストはそれだけ複雑さを上げる要因となる。

  • コンポーネントレベルでは、欲しい情報を得るためにネストをたどる必要がある
    • これは適宜 container component にすることでいくらか解決しそうだけど
  • reducer レベルでは、state 更新の際のマージ作業が複雑になる
  • 加えてパフォーマンスにも影響する

Normalizing State Shape · Redux
記事中でもリンク貼られてた Redux の公式ドキュメント。知らなかった。

これは今読んでる The Complete Redux Book にも書いてあった。


raw data だけ state に保存する(Storing only raw data in the state

記事中では Redux アプリにおける data には2種類あるとしていて、

  1. raw data:アプリが必要とするデータ。API 叩いて fetch したデータなど
  2. derived data:raw data から計算することで得られるデータ
    • firstNamelastName からユーザーの Name を得る、のような

後者の derived data は raw data からいつでも導出できるので、わざわざ state に保存する必要はない。
state に何か新しい情報を追加するときは、「これって現在の state の情報から作れない?」というのを自問してみるといいとのこと。


React の state よりも Redux の state を使う(Prefer Redux state over React state

React にも state 管理のしくみはあるが、 state の大部分は Redux の state として持たせた方が一貫性があって良い。特にチーム作業においては。

とはいえ例外として React の state で管理した方がいいケースもある。たとえば、複雑な UI の状態など、アプリ全体には重要でないような state を管理するようなとき。


Actions

action の payload は標準化する(Standardize action payloads

特にチーム開発においては、action の型というものも統一しておいた方がいいよねという話。
私も使っているけど Flux Standard Action に従っておけば大体問題ないんじゃないかな。


Action は composable にする(Ensure action creators are composable

composable は「組み立て可能な」ぐらいの意味?
既存の Action を組み合わせたより複雑な Action を作るというケースが実際のプロダクトだとよくあり、そのために Action のインターフェースを統一しておくと良い。
著者は全部 Promise でラップするようにしていて、そうすると全ての Action は then でつなげられる。

redux-thunk 使ってるとそんなに意識することがない?


Component Architecture

Containers & presentational components

コンポーネントの見た目に関する責務を持つ presentational component(純粋な React コンポーネント)と、コンポーネントの振る舞い?に関する責務を持つ container component(connect() したコンポーネント)を明確に分ける。

container と presentational という用語、公式ドキュメントにちゃんと書いてあったの知らなかった。
Usage with React · Redux


コンポーネントツリーの中間でも適切に container を使う(Use intermediary containers

コンポーネントツリーのどこを container にするかはアプリ作ってると悩まされる問題だけど、container にしていいのはアプリの root コンポーネントだけという制約はなくて、むしろそうすると末端のコンポーネントに必要な props を中間のコンポーネントがひたすら “passing through” するだけの無駄が発生する。

こういった不吉な臭いを感じ取ったら適宜 container 化していくといいんじゃないか。

Container にすることによるメリット・デメリットは kuy さんのこの記事が大変わかりやすい。

簡単に言うと connect() した container コンポーネントは Redux に依存することになるので、純粋な UI パーツとして再利用可能なコンポーネントにはならなくなる。そのトレードオフで判断するといい。

IDDD本もくもく読書会をやってみたメモ#1

はじめに

経緯など

教材

最初に読む書籍はこれにした。IDDD 本などと呼ばれたりする。

実践ドメイン駆動設計

実践ドメイン駆動設計

おそらく「ドメイン駆動設計とは何か」を知るためには エリック・エヴァンスのドメイン駆動設計 (IT Architects’Archive ソフトウェア開発の実践) を最初に読んだ方がいいんだろうけど、

  • IDDD本の方が発売日が新しく、取り上げているソフトウェアの例とかも馴染みやすいのではないか
  • DDD 本はコードがほぼ出てこない(と聞いている)のでどうしても話が抽象的になりがちで、挫折するんじゃないか

という理由でこちらにした。

なお、後から気づいたんだけど CodeZine で IDDD 本をかいつまんで説明した連載記事があり、
各章に入る前に一度こちらの記事を読んでから書籍に入った方が理解が早い気がしている。

IDDD本から理解するドメイン駆動設計連載一覧:CodeZine(コードジン)


もくもく読書会の進め方

第1回は本当に手探りで、19 時に集まってだらだら喋った後 19:30 から各自その場でもくもくと読書。
20:30 過ぎぐらいから(力尽きたのもあって)読めた範囲についてお互いの理解を確かめるために自由なディスカッションとなった。

第1回で読んだ範囲

第2章から読み始めた。

  • 2.8 全体像
  • 2.9 なぜそれほどまでに戦略的設計を重視するのか
  • 2.10 実世界におけるドメインサブドメイン
  • 2.11 境界づけられたコンテキストの意味を知る

2.11 あたりで頭に全然入ってこなくなったのでギブアップ。


学習メモ

以下、今回学んだことを個人的にメモ。

IDDD本の第2章「ドメイン」「サブドメイン」「境界づけられたコンテキスト」を読み解く (¼):CodeZine(コードジン)
も一緒に読む。


キーワード


ユビキタス言語

ドメインエキスパート(そのドメインについて一番詳しい人)や開発者を含めたチーム全体で作り上げる共有言語のこと。
チーム全体で「A という用語は B という意味である」というぶれない共通認識を作り上げることが大切。


ドメイン

ドメインとは、広い意味で言うと、組織が行う事業やそれを取り巻く世界のことだ。

ソフトウェアを作るときには必ず対象となるビジネスがあるはずで、その領域のことだという理解。


コアドメインサブドメイン(支援サブドメイン、汎用サブドメイン

ソフトウェアが対象とする事業領域を1つのドメインと捉えて全部入りのドメインモデルを構築すればいいかというとそうではなくて(むしろ逆で)、事業のドメインを分野ごとに適切に小さなドメインに分割していって、分割したドメインを組み合わせて全体を構築することになる。
この分割したドメインコアドメインサブドメイン と呼ぶ。

コアドメインドメイン全体の中でも事業的に最も重要な部分。
ビジネス的に最も価値があり、他社との競争を行う上で差別化要因となる部分のこと?

コアドメインではない補助的な部分を サブドメイン と呼ぶ。
サブドメインはさらに、コアドメインほど需要ではないが業務に不可欠なものを 支援サブドメイン、業務上特別なことはないがシステム上必要なドメイン汎用サブドメイン と呼ぶ。


境界づけられたコンテキスト

境界づけられたコンテキストは明示的な境界であり、ドメインモデルがどこに属するのかを表すものである。ドメインモデルは、ユビキタス言語をソフトウェアモデルとして表したものだ。

これがまだうまく言葉で説明できない。
別の書籍や web 上の解説を読むと「ユビキタス言語が適用できる範囲」などと言われてたりする。

例として本書では「アカウント」という用語を取り上げている。
アカウントという用語は、銀行取引コンテキストでは「口座」という意味になるが、文学コンテキストでは「報告書」という意味になる。

それぞれのアカウントの特徴は、名前だけでは区別できない。区別するには、それぞれが属する概念的なコンテナ、つまり境界づけられたコンテキストに注目する必要がある。これを見てはじめて、両者の違いを理解できるというわけだ。

境界づけられたコンテキスト内では、それぞれの用語はただ1つの意味を持つようになる。すなわち、ユビキタス言語が複数の意味を持たないようになる。


「境界づけられたコンテキスト」と「コアドメインサブドメイン」の関係性

上述したように境界づけられたコンテキストの中では用語についての意味がブレず、ただ1つに決まることが望ましい。ので、コアドメインサブドメインとは1:1の関係になることが望ましい設計といえる、はず。


わからなかったこと

ここで出てきた言葉や概念についてはなんとなくわかった気になれたが、結局

についてはわかっていない。最後の項目については後半で明らかになりそうだけど。


今後の予定

次回は 7/5(水) 19:00〜 にまた集まることになった。

順番にいくと次は2章の残りと3章なんだけど、これはもくもく会と言いつつ一度は各自で目を通してこないとついていけなくなるねーという話になり
できる範囲で読んでくることになった。

とばしてしまった1章も読んだ方がいい気がしてきたので、次回までに頑張って読みたい。。。!

参加者募集してますので、同じように DDD 興味あったけど今まで手を出せてなかったーとかって人がいたらぜひ一緒に勉強しましょう。お気軽にご連絡ください。


Slack グループ

読書会中に話したことをまとめたり、それ以外に書籍を読んでいてわからなかったことなどを共有するために Slack グループを作った。

https://iddd-mokumoku.herokuapp.com/

誰でも参加できるのでよかったらどうぞ。