オブジェクト指向をより理解するために「なぜ,あなたはJavaでオブジェクト指向開発ができないのか」という本を読みました。
メモ程度の内容ですが、まとめたので共有します~
〇感想
なぜオブジェクト指向で書くかという部分への理解が深まりました。オブジェクト指向でコードを書く目的を理解した上でのプログラミングがよりできると思います。
オブジェクト指向が何かという前に、どういう考えのもとでオブジェクト指向プログラミングをするかというのは重要ですね。
(じゃんけんやカードゲームに例えたらどういう役割なのかとか、非オブジェクトとの比べたときのメリットとか)
<1章>
プログラミングの手順
①コンピュータに行わせたいことを理解する
②理解したことを説明できるレベルまで整理する
③コンピュータに分かる言葉に翻訳する
<2章>
オブジェクト指向って本当に必要なのかについて
プログラミングにおいて最も難しいのは、一度作ったソフトウェアに対して変更や機能追加をしていくこと。
ex) 名前を付与する、数を増やす、仕様を追加する
非オブジェクト指向だと拡張が大変になる!
→拡張性をよくする、変更を素早く、簡単にする
拡張性については、非オブジェクト指向で開発したときに不足している点だと感じました。また、ただオブジェクト指向で開発するのではなく、拡張性を意識した上で開発するとまた開発内容が変わってくるのかなと思います。
<3章>
オブジェクト指向=人間の理解をできるだけ分かりやすくコンピュータに伝える方法
役割分担を明確にするということがオブジェクト指向による考え方の第一歩
役割=クラス担当者=インスタンス(オブジェクト)
属性とは、インスタンス固有の性質(人であれば、それぞれの名前やジャンケンに勝った回数など)
各クラスでそれぞれの対象の属性(性質)を管理させる
カプセル化
→処理が保護される
→外部から見て、どのような処理内容かを気にする必要がなくなる
アクセス修飾子・・・どこからアクセス可能であるかを決める(private, publicなど)
コンストラクタの特徴
インスタンスが生成されたときに自動で呼び出される。
メソッド名がクラス名と同じ
戻り値を返せない
JavaDoc→Javaでドキュメントを自動生成できる
オブジェクト・・・互いにメッセージを送り合うことによってシステムを構成
クラス・・・オブジェクトの役割(属性(フィールド)と操作(メソッド))を定義
属性・・・オブジェクトが固有に持つ性質(例、名前)
操作・・・メッセージをきっかけとしたオブジェクトの振る舞い(メソッド)
インスタンス・・・クラスという役割に対する実態(「プレイヤー」という役割(クラス)に対する「山田さん」という登場人物(インスタンス))
各単語の意味などは分かっているつもりでしたが、誰かに説明しようとするとなかなか難しいものだと思います。積極的に業務中に使っていきたいと思います。
<4章>
クラスの継承
・多くの共通部分と少しの異なる部分がある
・外から見れば同じ扱いだが、中から見れば別の扱いをする
メソッドの上書き・・・オーバーライド
継承したクラス・・・サブクラス
継承元のクラス・・・スーパークラス
<5章>
インタフェース・・・仲立ち、オブジェクトへの操作方法とそれに対応する振る舞いの組み合わせを規定
インタフェースを実装する(implement)
自動車であれば、インタフェースは「右側のペダルを踏むとアクセル、左側を踏むとブレーキ」のような操作と振る舞いになる。
大規模になる場合は、インタフェースで実装することで変更を加える手間が少なく済む。
インタフェースも使いどころを考えながら利用していければと思います。これも拡張性など考慮すると使ったほうがいい場面がある気がしますね。あとは、実際に拡張することを考えると初めにどのようにインタフェースを定義するかも重要に思います。
<6章>
モデリング手順
1仕様を決める
2クラスを抽出
3オブジェクト間のメッセージを考える
4各クラスの操作を洗い出す
5各クラスの属性を洗い出す
モデリングとは、「物事を本質をできるだけシンプルに表現すること」。
モデリングに正解はない。
行きつ戻りつしながらよりよいモデルを練っていくしかない。
ユースケース・・・ユーザがどのように利用するかというシナリオを文章として記述
名詞抽出法・・・概要説明などを書いてみて、その中から名詞を抽出して、その名詞からクラスを選定。
シーケンス図・・・オブジェクト同士による操作の呼び出し関係を図にしたもの
UML・・・統合モデリング言語(Unified Modeling Language)
『モデリングに正解はない』という部分が大事かと思いました。どうクラスの操作や属性を決めるかは悩むポイントですが、やっていく中で改善していければと思います。
Nightwatchを解説&導入してみる① 初めの一歩 サンプルコード有
環境:Windows10, Nightwatchのバージョン:1.7.11
Nightwatchとは
NightwatchというjavascriptのE2Eのテストフレームワークを導入してみます。
Nightwatch公式サイト
https://nightwatchjs.org/
Nightwatchとは、日本語で宿直とかいう意味ですね。夜、見回りをする警備員さんのことです。
E2E はEnd to Endの略です。
End to End は何かといいますと、ここでの意味は、ユーザーがする動作のようなテストを行うことですね。
例えば、ログインのページだと
「ユーザーがログインIDとパスワードを入力欄に入力し、ログインボタンをポチっと押すと、ログイン後のページに遷移して『ようこそ 〇〇様』と表示する。」
という流れをテストすることです。
ユニットテストがプログラムのメソッド単位でテストを行うことに対比して言われることが多いでしょう。
さて、先の例で行きますと
「ユーザーがログインIDとパスワードを入力欄に入力し、ログインボタンをポチっと押すと、ログイン後のページに遷移して『ようこそ 〇〇様』と表示する。」
このテストを人間がいちいち行うのはまったくもって手間ですよね!
しかも、複数ブラウザで行うとかなると、手間×4ぐらいあります。
なので、ここは自動化しようというわけです。
余談ですが、弊社ではこのテストをGhost Inspectorというツールを使ってやっていました。が、 Ghost Inspector はソースコードの品質を高めるために役に立っているかというと、そうではなく、サービスの死活監視的に用いられているので、今回改めてもうちょっとjavascriptのプログラム的なテストフレームワークを導入してみようかなと思った次第です。(意識高く!(`・ω・´))
表題は導入してみる、となっていますが、本サービスに導入するかどうかは今の時点で未定です。(笑) ただ、開発環境としては作ってみたというところですね。
私自身はjavascript界からちょっと離れていました。
(こいつ、いつもこれ言ってんな…。(´ω`))
初心者なので、間違っているところ等ありましたら教えてください。
ではいよいよ、Window10にNightwatchをインストールします。
手順① Nightwatch をインストールするディレクトリを作る
まっさらな洗い立てのシーツのようなディレクトリを作ります。
ここでは、
D:\study\nightwatch
に作りました。
手順② Nightwatch自体をインストール
コマンドプロンプトを開いて、そのディレクトリ内に移動し、
npm install nightwatch
とやります。
すると下記の図のようにいろいろメッセージが表示されてインストールができます。
もし、npmがわからない、npmが動作しない場合は、node.jsとnpmをインストールして使えるようにしてください。
npmとはパッケージマネージャーと呼ばれるツールで、こういうフレームワークをインストールする場合などに使います。ナウなjs界では欠かせない存在なので、躊躇せずにインストールしてみてください。
これで、Nightwatch自体がインストールされました。
手順③ ChromeのWebdriverをインストール
同じディレクトリで
npm install chromedriver --save-dev
とやります。すると、下記のように表示されて、インストールが終わります。
これは何をインストールするのかというと、ChromeのWebdriverをインストールしています。手っ取り早く言うと、これがChromeを動作させてして、テストの時に使ってくれるわけです。
手順④ Selenium Serverインストール
次に同じディレクトリで、次のようにやります。
npm install selenium-server --save-de
すると、上記のようにメッセージが表示されて、インストールができます。
今回は、Selenium Serverというのをインストールしました。 Selenium Server は何かというと、③のChrome DriverはChromeを動作させますが、ほかのブラウザなどを動作させたりします。(現時点ではいらないのかもしれない)
これで、インストールするものは一通り終わりです。
手順⑤設定などを準備する
まだ設定などが必要です。さっきから使っている
D:\study\nightwatch
の中に、node_modulesというディレクトリができていて、ここにウジャッといろいろなモノが入っています。その中のnightwatchというディレクトリに、examplesというディレクトリがあって、そこにサンプルが入っています。ありがたや~
D:\study\nightwatch\node_modules\nightwatch\examples\tests
testというディレクトリの中の、ecosia.js というファイルをコピーして、nightwatchディレクトリの中に、test というディレクトリを作って、そこに貼り付けます。
このようになっているはずです!
ecosia.jsを参考までに貼っておきます。ちな、ecosiaはドイツの検索エンジン?らしいです。
describe('Ecosia.org Demo', function() {
before(browser => browser.url('https://www.ecosia.org/'));
test('Demo test ecosia.org', function (browser) {
browser
.waitForElementVisible('body')
.assert.titleContains('Ecosia')
.assert.visible('input[type=search]')
.setValue('input[type=search]', 'nightwatch')
.assert.visible('button[type=submit]')
.click('button[type=submit]')
.assert.containsText('.mainline-results', 'Nightwatch.js');
});
after(browser => browser.end());
});
これがテストスクリプトです。解説すると、
というサイトを開き、タイトルが ’Ecosia’ で、検索ボックスにnightwatchと入れて検索して、クリックすると、Nightwatch.js という文字列が含まれるページが出てくる、というテストですね。
次の2つのファイルをD:\study\nightwatch に追加します。
global.js
const chromedriver = require('chromedriver');
module.exports = {
before: function (done) {
chromedriver.start();
done();
},
after: function (done) {
chromedriver.stop();
done();
}
};
使うWebdriverなどを指定しています。
nightwatch.conf.js
// Autogenerated by Nightwatch
// Refer to the online docs for more details: https://nightwatchjs.org/gettingstarted/configuration/
const Services = {}; loadServices();
module.exports = {
// An array of folders (excluding subfolders) where your tests are located;
// if this is not specified, the test source must be passed as the second argument to the test runner.
src_folders: ["test"],
// See https://nightwatchjs.org/guide/working-with-page-objects/
//page_objects_path: 'page-objects',
// See https://nightwatchjs.org/guide/extending-nightwatch/#writing-custom-commands
custom_commands_path: '',
// See https://nightwatchjs.org/guide/extending-nightwatch/#writing-custom-assertions
custom_assertions_path: '',
// See https://nightwatchjs.org/guide/#external-globals
globals_path : './globals.js',
webdriver: {},
test_settings: {
default: {
disable_error_log: false,
launch_url: 'https://nightwatchjs.org',
screenshots: {
enabled: false,
path: 'screens',
on_failure: true
},
desiredCapabilities: {
browserName : 'firefox'
},
webdriver: {
start_process: true,
server_path: (Services.geckodriver ? Services.geckodriver.path : '')
}
},
firefox: {
desiredCapabilities : {
browserName : 'firefox',
alwaysMatch: {
acceptInsecureCerts: true,
'moz:firefoxOptions': {
args: [
// '-headless',
// '-verbose'
]
}
}
},
webdriver: {
start_process: true,
port: 4444,
server_path: (Services.geckodriver ? Services.geckodriver.path : ''),
cli_args: [
// very verbose geckodriver logs
// '-vv'
]
}
},
chrome: {
desiredCapabilities : {
browserName : 'chrome',
'goog:chromeOptions' : {
// More info on Chromedriver: https://sites.google.com/a/chromium.org/chromedriver/
//
// This tells Chromedriver to run using the legacy JSONWire protocol (not required in Chrome 78)
w3c: false,
args: [
//'--no-sandbox',
//'--ignore-certificate-errors',
//'--allow-insecure-localhost',
//'--headless'
]
}
},
webdriver: {
start_process: true,
port: 9515,
server_path: (Services.chromedriver ? Services.chromedriver.path : ''),
cli_args: [
// --verbose
]
}
},
edge: {
desiredCapabilities : {
browserName : 'MicrosoftEdge',
'ms:edgeOptions' : {
w3c: false,
// More info on EdgeDriver: https://docs.microsoft.com/en-us/microsoft-edge/webdriver-chromium/capabilities-edge-options
args: [
//'--headless'
]
}
},
webdriver: {
start_process: true,
// Download msedgedriver from https://docs.microsoft.com/en-us/microsoft-edge/webdriver-chromium/
// and set the location below:
server_path: '',
cli_args: [
// --verbose
]
}
},
//////////////////////////////////////////////////////////////////////////////////
// Configuration for when using the browserstack.com cloud service |
// |
// Please set the username and access key by setting the environment variables: |
// - BROWSERSTACK_USER |
// - BROWSERSTACK_KEY |
// .env files are supported |
//////////////////////////////////////////////////////////////////////////////////
browserstack: {
selenium: {
host: 'hub-cloud.browserstack.com',
port: 443
},
// More info on configuring capabilities can be found on:
// https://www.browserstack.com/automate/capabilities?tag=selenium-4
desiredCapabilities: {
'bstack:options' : {
userName: '${BROWSERSTACK_USER}',
accessKey: '${BROWSERSTACK_KEY}',
}
},
disable_error_log: true,
webdriver: {
timeout_options: {
timeout: 15000,
retry_attempts: 3
},
keep_alive: true,
start_process: false
}
},
'browserstack.local': {
extends: 'browserstack',
desiredCapabilities: {
'browserstack.local': true
}
},
'browserstack.chrome': {
extends: 'browserstack',
desiredCapabilities: {
browserName: 'chrome',
chromeOptions : {
w3c: false
}
}
},
'browserstack.firefox': {
extends: 'browserstack',
desiredCapabilities: {
browserName: 'firefox'
}
},
'browserstack.ie': {
extends: 'browserstack',
desiredCapabilities: {
browserName: 'internet explorer',
browserVersion: '11.0'
}
},
'browserstack.safari': {
extends: 'browserstack',
desiredCapabilities: {
browserName: 'safari'
}
},
'browserstack.local_chrome': {
extends: 'browserstack.local',
desiredCapabilities: {
browserName: 'chrome'
}
},
'browserstack.local_firefox': {
extends: 'browserstack.local',
desiredCapabilities: {
browserName: 'firefox'
}
},
//////////////////////////////////////////////////////////////////////////////////
// Configuration for when using the Selenium service, either locally or remote, |
// like Selenium Grid |
//////////////////////////////////////////////////////////////////////////////////
selenium_server: {
// Selenium Server is running locally and is managed by Nightwatch
selenium: {
start_process: true,
port: 4444,
server_path: (Services.seleniumServer ? Services.seleniumServer.path : ''),
cli_args: {
'webdriver.gecko.driver': (Services.geckodriver ? Services.geckodriver.path : ''),
'webdriver.chrome.driver': (Services.chromedriver ? Services.chromedriver.path : '')
}
}
},
'selenium.chrome': {
extends: 'selenium_server',
desiredCapabilities: {
browserName: 'chrome',
chromeOptions : {
w3c: false
}
}
},
'selenium.firefox': {
extends: 'selenium_server',
desiredCapabilities: {
browserName: 'firefox',
'moz:firefoxOptions': {
args: [
// '-headless',
// '-verbose'
]
}
}
}
}
};
function loadServices() {
try {
Services.seleniumServer = require('selenium-server');
} catch (err) {}
try {
Services.chromedriver = require('chromedriver');
} catch (err) {}
try {
Services.geckodriver = require('geckodriver');
} catch (err) {}
}
テストのプログラムはtestディレクトリにあるよ、などが書いてあります。
手順⑦テストを実行
ハァハァ、いよいよ…。テスト実行です。D:\study\nightwatchで
.\node_modules.bin\nightwatch -v
とやります。
おおっ!
OK. 5 assertions passed. (1.818s
と表示されましたね!!
そして、皆さんの目の前で、ブラウザっぽい画面が一瞬開いて検索っぽいことをやっているのが見えたでしょうか?
今回は基本のキでインストールして動作させるところだけで終わってしまいました。
エリック・エヴァンスのドメイン駆動設計を読みました③
■蒸留
蒸留とは混ざり合ったコンポーネントを分離するプロセス。
モデルとは知識が蒸留されたもの。
今までの章で行ってきたドメインモデルの洗練も蒸留ではあると思いますが、この本の15章の蒸留はここまで洗練してきたドメインモデル群を更に大事なものから優先順位をつけて分離していくことを言っていると思います。
この蒸留の目的は、システム内で最大の価値を付加すべきものをたった1つ抽出すること。
抽出されたものがコアドメイン。
そのコアドメインは当然我々のソフトウェアを特徴づけ、構築する価値のあるものにする。
化学の蒸留と同様に、蒸留プロセスにおいて分離されたかなり価値がある副産物(汎用サブドメインなど)が出来上がる。
・コアドメイン
ドメインに関心があって技術的にも優秀なメンバーがコアドメインに当たるべきとのこと。
ただ技術的に優れていてもドメインに関心がない人が多い傾向にあるためなかなかそんな人はいない。
ただそういう場合はドメインに関心がある優秀なメンバーを集め、ドメインエキスパートが参加するチームを収集し補佐する。
コアドメインの選択はその「システム内で最大の価値を付加すべきもの」なので、システムによって様々。
・汎用サブドメイン
システムを機能させて、モデルを完全に表現するためには欠かせないもので補佐役を果たす。
相当数のビジネスで必要になる概念を抽象化している。
例としては、企業の組織図、財務に関するもの、タイムゾーンなど。
汎用という言葉があるがこれはコードが再利用可能ということではない。
再利用性を考慮していては、蒸留のコアドメインに集中するという動機から外れてしまう。
汎用的な概念の範囲内に収めるということには集中する。
・ドメインビジョン声明文
コアドメインとそれをもたらす価値に関する簡潔な記述を作成する
チームに共通の方向性を与える。
新しい洞察を得たら改訂すること。
ドキュメントって最初は気合い入れて作るけど、改訂を怠りがちになりますよね。。。
・強調されたコア
ドメインビジョン声明文は広い観点から見たコアドメインを識別するもの。
強調されたコアは具体的なコアドメインの要素の識別をするための文書。
これがないと個人の解釈によって識別することになってしまうため、好ましくない。
記述方法として「蒸留ドキュメント」や「コアにフラグを立てる」というものがある。
蒸留ドキュメントはコアドメインとコアを構成する要素間の主要な相互作用を簡潔に記述する。
コアにフラグを立てるというのはUML図などでコアとなる要素に印をつけておくこと。
・凝集されたメカニズム
オブジェクト思考設計にとってカプセル化は標準的なプラクティス。
「何が」(what)と「どのように」(how)を分離するが、「何が」(what)が、「どのように(how)」によって肥大化し、複雑化することがある。問題を解決するためのアルゴリズムが多くなって問題を表現するメソッドがわかりにくくなる。
こういうことが多くなってきたらモデルに問題がある兆候。
この問題を解決するためのアルゴリズムなどを概念的に凝集された部分を切り分けて、フレームワーク化する。
汎用サブドメインも凝集されたメカニズムもコアドメインの負担を軽減したいという願望に基づいているが、汎用サブドメインは表現力豊かであるモデルで、凝集されたメカニズムはドメインを表現しない。
モデルにより提起された面倒な処理の問題を解決するためにある。
・隔離されたコア
モデルをリファクタリングして、補助的な役割を果たすものから分離すること。
そうすることで凝集度が高まり、他のコードへの結合が低くなる。
隔離するために
- コアサブドメインを識別する
- 関連するクラスを、それに関係づけている概念に由来する新しいモジュールへ移動する。
- コードをリファクタリングして、概念を直接表現していないデータと機能を切り離す。
- 他のモジュールとの関係を最小限におさえて明確化する。
- 1から繰り返す
・抽象化されたコア
モデルにおけるもっとも基本的な概念を識別し、それを別のクラス、抽象クラスまたはインターフェースに括り出す。
この抽象的なモデルは、重要なコンポーネント間をほとんど表現するように設計する。
この抽象的なモデル全体を独自のモジュールに入れる。
ただし、特殊で詳細な実証クラスは、サブドメインによって定義されたモジュールに残す。
■大規模な構造
ようやく森を見る。
森を見るためにそれぞれの木の役割や関係性を明確にしておく。
概念上の大規模な構造をアプリケーションと共に進化させ、場合によって、全く別の種類の構造に変更する。
大規模な構造は一般に、境界付けられたコンテキストを横断して適用できる必要があり、現実の制約にも対応しなければならない。
大規模な構造を適用すべきなのは、モデルの開発に不自然な制約を強いることなく、システムを大幅に明確化する構造が見つけられたときでうまくいかなければ別になくてもよい。
・責務のレイヤ(システムのメタファ、知識レベル、着脱可能なフレームワークは略)
責務駆動設計は大規模な構造に対しても適用される。
責務駆動設計と同じように、上位層は下位層に依存するが、下位層は上位層からは独立するような形。
レイヤ化すると見通しがよくなるかもしれない。
過度なレイヤ化は逆にわからなくなるので注意。
モデルの依存関係を調べ、ドメインに自然な階層が認められたら責務を与える。
■感想
2019年12月、自分は初めてPHPカンファレンスに参加しました。
その初ペチコンで初めて受講した講義が、
MVCにおける「モデル」とはなにか
https://fortee.jp/phpcon-2019/proposal/b6302d4a-a8b8-41a7-ad0c-98704fd18c6c
でした。
正直内容が難しく、後々の講義もこのレベルが続くのか…と絶望していました。
後々、参加者の感想を見ると熟練者の方でもこのトークは難しいレベルだったみたいです。
ただ今回、当時のトークで
(飲食店等において)
ホールの人は「客とオーダーが満たされているか」が関心事であり、
キッチンの人にとっては「料理をどの順番で提供しなければならないかが関心事」
と聞いたことを思い出し、この関心事に注目してモデリングすべきなんだと本を読みながら感じることが出来ました。
誰にとっては何が値オブジェクトで何がエンティティなのかとかですね。
当時わからなかったことが少しでもわかるようになったのは嬉しかったです。
設計の本を読むとプログラマに最も必要なのはコミュ力ではないかと思ってきますね。
どうしてコミュ障に優しい世界じゃないんですか…?
エリック・エヴァンスのドメイン駆動設計を読みました②
第4部 戦略的設計
システムがだんだん複雑になってくると、巨大なモデルを扱ったり把握する必要が出てくる。
そうした状況下では単一の巨大なユニットとして管理するのは難しいため、分解しなければならない。
ここで、小さなシステムに分解していくときに、陥りがちなのが全体が見えなくなること。
「木を見て、森を見ず」と述べられている。
そうならないように以下の3つのテーマを考察している
- コンテキスト
- 蒸留
- 大規模な構造
■コンテキスト
コンテキストについて見ていく前にそもそもコンテキストってなんだって感じですよね。
androidの開発をやっていても当たり前のように出てきます。
辞書を引いても何とも曖昧でややこしいです。
ロングマン先生に依ると
1 the situation, events, or information that are related to something and that help you to understand it
2 the words that come just before and after a word or sentence and that help you understand its meaning
とあります。
日本語でも文脈、前後関係、事情、背景、状況と訳されたりしています。
少し具体的な例を挙げると、
私がテレビを見ていてそこには芸能人が映っているとします。
そこで近くにいる家族が
「ちょっと手伝って~」
と言ったときに、テレビに映っている芸能人に言っているとは思わないですよね?
手伝えるのはテレビを見ている私の方です。
これはコンテキストに依るものだと思います。
もう1つ挙げると…
「ほげ」
と聞くと何を思い浮かべますか?
プログラミング経験がある人やIT系の人だとサンプルコードなどでよく見かける
「hoge」
が浮かびますよね?
そうでない人だと
「何かのリアクション?」「捕鯨?」
みたいなこと思い浮かべるかもしれません。
商品と聞いて大体の人は商品そのものを浮かべるけど、配送業の人だと中身が入った段ボールのことを思い浮かべるみたいなことを聞いたことがあります。
同じ用語でも、受け取り手や場面などによって認識が変わってくるのはコンテキストに依るものだと認識しています。
コンテキストの内容に入っていきます。
境界づけられたコンテキストによって各モデルが適用できる範囲を定義する。
コンテキストマップによってそれぞれのコンテキストとコンテキスト間の関係性を全体的に概観できる。
継続的な統合によってモデルの統一が保たれる。
・境界付けられたコンテキスト
名前の通りコンテキストはあいまいでなく明示的に境界付ける。
明示的な境界は、チーム編成、そのアプリケーションに特有の部分が持つ用途、コードベースやデータベーススキーマなどの物理的な表現などの観点から設定する。
その境界内では、モデルを厳密に一貫性のあるものに保つ、また外部の問題によって注意をそらされたり、混乱させたられたりするのは避ける。
定義することで得られるものは明確さ。
そのコンテキスト外の人達は、コンテキスト内の事は明確に区切られているのであれば考えなくて済む。
ただし、境界を知っておかなければ、知らずに踏み込んでしまう可能性があるからそれは注意が必要。
接点があるコンテキスト間も同様。
・継続的な統合
境界づけられたコンテキストを定義したらそれを安定させなければならない。
継続的な統合はコンテキスト内で、2人以上での作業をする際には頭に入れておく。
絶えずそのコンテキストチーム内で以下2つの統合を行う。
- モデル概念の統合
ユビキタス言語を継続的に練り上げる。そのためにメンバ間で絶えずコミュニケーションを行う。 - 実装の統合
段階的に行われ、再生可能なマージ、ビルドテクニック
自動化されたテストスイート
変更が統合されない期間に対して、妥当な短さの上限を設定するルール
・コンテキストマップ
コンテキストを境界づけても全体的な視点を得られたとは言えない。
境界づけた時点での全体的視点は得られているかもしれないが、それぞれのコンテキスト内で統合を絶えず行っているため、得られているとは言い難い。
変更により境界が明示的でなくなる場合がある。
境界づけられたコンテキスト間のコードの再利用もそういった理由で避けるべき。
全体的な視点を得るためにコンテキストマップを利用する。
コンテキストマップの利用にあたり、以下のルールを守る
境界づけられたコンテキストにそれぞれ名前をつけ、その名前をユビキタス言語の一部にする
現在存在する領域の地図を書く、変換にはそのあと取りかかる
モデル同士の接点を記述して、あらゆるコミュニケーションで必要となる明示的な変換について概略を述べ、共有するものがあれば強調する
マップは常にありのままの状況を表すこと
実際に変更が完了するまではマップを変更しないこと
コンテキスト内であればユビキタス言語が方言化(同じ言葉でもコンテキスト外の人が使っているものとニュアンスの差異があることがある)することもあるが、境界づけられたコンテキストそのものに名前をつけることでどのコンテキストで作業している人でもあいまいにならずに済む。
これらが保たれると以下のより効果的な戦略へと移行できるようになる。
1. 共有カーネル
2つのコンテキスト間(あるいは複数のコンテキスト間)でモデルの一部を共有すること。
カーネルというとそれぞれのモデルの中核を共有するようにとらえるかもしれないが、モデル、コード、モデルの該当箇所に関連するデータベース設計なども含む。
注意点としては、共有している部分は共有していないコンテキスト内の変更に比べて頻度は下げること、他チームの相談なしに行わないこと。
2. 顧客(下流)/供給者(上流)の開発チーム
2つのコンテキストA, Bがあって、BがAからの入力を受け取るなどAがBに何かを要求する場合に顧客/供給者の関係を取ることがある。
A(供給者) → B(顧客)
この場合に、BがAの変更に対して、一方的に受け入れるだけならば簡単だが、Bが拒否権を持っていたりやAに対して何か手続きをさせるような場合は両者が円滑に作業できるようにしなければならない。
そのために…
・顧客の要求が最優先
・供給者チームが顧客を壊してしまわないかと恐れることなくコードを変更できるようにし。顧客チームが上流チームのことを常に気にすること自分の仕事に集中できるようにするには、自動化されたテストがないといけない。
3、4、5は顧客/供給者の関係にありながら顧客の要求に応える動機がない場合、2のことができないので検討する。
顧客の要求に応える動機がないというと攻撃的だが以下のような場合もある
・供給者が多くの顧客を抱えている
・2つのチームが経営階層上で遠く離れている
・昔からの顧客に市場の方向性が変わったため価値が見いだせない
など
3.順応者
一言でいうと上流チームに順応する。
上流チームのモデルに隷従しユビキタス言語を供給する。
下流チームの自由度は低くなるがシンプルになる。
下請けとかこんな感じかなと思う。
上流チームの設計が悪いと下流チームも悪くなるのでその辺りは気を付ける。
4.腐敗防止層
上流のモデルを下流で扱うのが困難な場合に検討する。
上流と下流の間にモデルの変換機能など腐敗防止層を用意してあげる。
そのインターフェースはデザインパターンのファサードパターンやアダプタパターンなどで構成されたりする。
他のシステムとの連携でデータを変換する出入り口とかがイメージしやすい。
5.別々の道
上流と下流の関係だったが、そもそも別々で良い場合など。
これまで学んできた上流と下流の統合方法は全てオーバーヘッドが存在するため、コストが発生する。
よって、それに見合う利益を生み出さないといけない。
利益を生み出せない場合などに、統合が必要なのかを改めて考える。
それでも必要な場合は1~4のどれかを考える。
とりあえず別々の道を検討して無理または面倒ならば何らかの形で統合するとかでいいかも
6. 公開ホストサービス
とあるサービスが他の多くのサービスなどから利用される場合は、共通のサービスを用意する。
手間なので1つ1つについて変換サービスを構築しない。
特定のサービス向けに拡張することはある。
公開APIなど。
7. 公表された言語
統合するお互いのコンテキスト間の変換では共通する言語を利用する。
片方に合わせるとモデルが崩れる恐れがある。
世に回っているドキュメントなどで広く使われているものなどを見つけれるといい。
この辺なんとなく上流に合わせがちになっている気がする…
実際にこれらのパターンをどう使っていくかですが、
新しくプロジェクトを始めるにしろプロジェクトが進行中にしろ
コンテキストマップを描こうとすることが大事になってくると思います。
そのためにはコンテキストの境界を付けること、継続的な統合を行う。
その後は変換できないシステム(外部システムやレガシー過ぎて手がつけられないシステム)に線を引く。
・新しくプロジェクトを始めている場合
作成中のシステム内で継続的な統合が困難になれば共有カーネルを探す。
依存関係が一方向であれば顧客/供給者の開発チームを選択する。
外部のシステムと連携する場合は最初に検討すべきは別々の道。
コストが低いので。
統合が不可欠であるなら、順応者か腐敗防止層かを考える。
出来るだけ腐敗防止層で考えたい。
順応者は心情的によろしくないので。
・プロジェクトが進行中の場合
まず共有カーネルを見つける。
コンテキストの重複した出来るだけ小さいところから始めていく。
テストは必ず作成する。
次に継続的な統合を行い両方を理解した人を増やす。
コンテキスト間でメンバーをローテーションする。
その後にレガシーシステムを段階的に廃止する。
テスト戦略の決定は不可欠で腐敗防止層を経由してレガシーシステムと通信する
公開ホストサービスがあるなら公表された言語が使えないか検討する。
エリック・エヴァンスのドメイン駆動設計を読みました①
難解と言われながら名著とされている「エリック・エヴァンスのドメイン駆動設計」を読みました。
備忘録として自分なりの解釈を入れたりしてメモを残しておきたいと思います。
間違いもあると思うのでご指摘ください。
この記事では3部まで記載しています。
第1部 ドメインモデルを機能させる
ドメインモデルとは何かですが、それぞれドメインとモデルを以下のように認識しています。
ドメイン
プログラム、システムが関心を持つ領域や対象
モデル
物や事象、概念などを現実にあるものや現実で行っていることをベースに必要な部分を厳選して抽象化すること
モデルの必要な部分というのがドメインによって変化するので、ドメインによって精錬されたモデルがドメインモデル。
ドメインモデルを機能させるために…
・知識をかみ砕く
大量の情報から重要な情報を抜き出す、重要な言葉を見つけ出す(1人で行うのはNG) 抜き出すためには何が重要な情報かを見極めるためにその分野の学習が必要になる
・コミュニケーションと言語の使い方
チームで誤解のないように共通の言葉(ユビキタス言語)を使いながらモデル化を行う。
もし名前がなければ付ける。
その名前の物が何をするのか、チーム全体で共有する。
それが共有できないとコードの共有をするのも難しくなる。
・モデルと実装を結びつける
モデル駆動設計を行う。手続き型ではない。
モデル駆動設計
ソフトウェア要素のサブセットがモデル要素と密接に対応している設計。
相互に一致した状態を保つ。
第2部 モデル駆動設計の構成要素
・ドメイン駆動設計がアーキテクチャに求めること
レイヤードアーキテクチャなどドメイン層が分離したもの
・ドメインモデルの構成要素
ドメインモデルはエンティティと値オブジェクトとドメインサービスから構成される。
エンティティ…同一性(IDなどの識別子を持つ)を持つオブジェクト
値オブジェクト…エンティティとは逆で同一性がないオブジェクト。
同じ色で同じマジックが2つあったらどちらを使うのか気にしない
※同じモノでも視点や誰が使うかによって、同一性をもつかどうかは変わるので注意
ドメインサービス…エンティティや値オブジェクトとして扱うと不自然なもの
エンティティと値オブジェクトもドメインサービスとして扱うこともできるので節度を持って扱う
優れたサービスは以下の3点
- 操作がドメインの概念に関係しており、その概念がエンティティや値オブジェクトの自然な一部ではない。
- ドメインモデルの他の要素の観点からインターフェースが定義されている
- 操作に状態がない
第3部 より深い洞察へ向かうリファクタリング
リファクタリングはソフトウェア開発者にとっては良く知られている言葉で、機能の変更をしないようにソフトウェアの再設計を行うこと。
マーチン・ファウラーの著書である「リファクタリング」のような、ざっくり言うとコードをきれいにするのも重要だが、ドメインモデルの場合は、いわゆるクックブックのやり方を当てはめるだけでは済まない。
クックブックを当てはめることは良いことではあるが、当てはめてドメインモデルが改悪されてしまうと意味がなくなる。
適切なドメインモデルを念頭に創造力を持つこと、試行錯誤を繰り返すことがまず第一で、それが外れない範囲でパターンを適用する。
・ブレイクスルー
ブレイクスルーは起こすものではなくて結果的に起こるもの 継続的なリファクタリングをすることによって、コードやモデルが整えられる。
改良するたびに、開発者の視界は明確になってくる
視界が明確になったことにより、洞察のブレイクスルーをもたらす可能性が作り出される
ブレイクスルーの舞台を整えるために必要なこと
- 知識を噛み砕き、強固なユビキタス言語を育成するのに集中すること
- 重要なドメインの概念を探求して、それをモデルで明示すること
- 設計をよりしなやかになるように改良すること
- モデルを蒸留すること
・概念を掘り出す
ドメインエキスパートの使う言葉に耳を傾ける 以下のモデルにとって有益になりうる概念を示す手掛かりを見逃さないようにする。
- 何か複雑なものを簡潔に述べている用語がないだろうか?
- ドメインエキスパートに言葉の選び方を(たぶん、角が立たないように)正されていないか?
- あなたが特定のフレーズを使った時に、ドメインエキスパートたちの困惑した表情が消えることはないか?
制約やプロセスなどをモデル概念とすると設計が鋭くなる場合がある。
・より深い洞察へ向かうリファクタリング 重点的に取り組むべきこと
- ドメインに馴染む
- 常に物事に対して違う見方をする
- ドメインエキスパートとの会話を途切れさせない
リファクタリングを行う際はコードが整然としていることで安心しない。 ドメインモデルが適切かを常に考える必要がある。
モデルが適切でなかった場合、良いモデルについて探求する必要がある。 探求するには、より長い時間がかかり、より多くの人の参加が必要となる。 ドメインエキスパートや元々の開発者など、より多くの人とミーティングを行う。
ソフトウェアはユーザーのためだけのものではなく開発者のためのもの。
より深い洞察へ向かうリファクタリングは継続的なプロセス。 暗黙的な概念が認識されて明示的になる。 設計の一部はよりしなやかになる。 そして深いモデルへと突き進みまたリファクタリングを繰り返していく。