はじめに
読み込みと書き込みを分解して、セキュアにテストできたらなと思いまして、CQRSに目をつけました。用途としては、store moduleでcommandを叩こうかなと思っていました。
しかし、いざ書いてみると結構なボリュームで、とてもじゃないですが、少人数での開発をする場合、コードの綺麗さ以上にボリュームが多いことによりスピードダウンが感じられました。
また、Specパターンを適用しないと、やはりテストやら検証やら恩恵が少ないと感じましたので、これ以上増えるのはキツイかなと思ったので没になりましたが、いつか実践できたらと思っています。
以下の例はバリデーションをSpecパターンを適用しないで肥大化した例 and それのせいで最終validateがめんどくさくなった例です。疑似コードですが、import axios
より上はコピペでブラウザのコンソールとかで動くので、よければ軽く触って遊んでみてください。
疑似コード
class OverrideError extends Error {
constructor(...params) {
super(...params);
this.name = 'CommandError';
}
}
// commands.js
class Command {
isValid() {
// LaravelのFacadeで使われてた手法
throw new OverrideError('isValidメソッドをオーバーライドしてください!!!');
}
}
class CommandError extends Error {
constructor(...params) {
super(...params);
this.name = 'CommandError';
}
}
// commands/.js
class CandidateCommandError extends CommandError {
constructor(...params) {
super(...params);
this.name = 'CandidateCommandError';
}
}
// commands/candidate.js
class Candidate extends Command {
constructor(params) {
super();
// getter は console.log で見えなく、forでも回せないので、
// デバッグしやすいようにundefinedを突っ込んでおく
this._name = undefined;
this._age = undefined;
this._skillLevel = undefined;
if (!params) return;
if ('name' in params) this.name = params.name;
if ('age' in params) this.age = params.age;
if ('skillLevel' in params) this.skillLevel = params.skillLevel;
}
isValid() {
return Boolean(this.name && this.age && this.skillLevel);
}
get name() {
return this._name;
}
set name(name) {
// 名前は1文字以上の文字列
if (typeof name === 'string' && name.length > 0) {
this._name = name;
} else {
throw new CandidateCommandError('名前は1文字以上の文字列である必要があります。');
}
}
get age() {
return this._age;
}
set age(age) {
// 未成年は禁止
if (typeof age === 'number' && age >= 18) {
this._age = age;
} else {
throw new CandidateCommandError('年齢は18歳以上である必要があります。');
}
}
get skillLevel() {
return this._skillLevel;
}
set skillLevel(skillLevel) {
// スキルレベルは 0 ~ 10 の間
if (typeof skillLevel === 'number' && skillLevel > 0 && skillLevel <= 10) {
this._skillLevel = skillLevel;
} else {
throw new CandidateCommandError('スキルレベルは 0 ~ 10 の間である必要があります。');
}
}
}
// exception.js
import * as axios from 'axios';
class CommandHandlerError extends Error {
constructor(...params) {
super(...params);
this.name = 'CommandHandlerError';
}
}
// commandHandlers/candidateHandler.js
async function CandidateHandler(candidate) {
if (!(candidate instanceof Candidate)) {
throw new CommandHandlerError('Candidateインスタンスではありません。');
}
if (!candidate.isValid()) {
throw new CommandHandlerError('Candidateの仕様を満たしていません。');
}
// 本来はrepositoryを使うが、イメージでお願いします
const response = await axios.post('/api/candidate', candidate).catch(e => e.response);
return response;
}
最後に
大規模開発ではCQRSをして、整理したいですね〜〜〜
CQRSの場合、query store として、 firebase database や firestore とかととても相性が良い気がしてます。