手順から始めるオニオンアーキテクチャ
LTで語るにはパッションと尺が足りず、勉強会で詳しくやるにはニッチすぎる技術的な話題が頭の中に燻っているので、たまにこちらに吐き出していきたいと思いました。
明確に誰かが読むことを想定していない自由研究のようなものですが、この文字の多さはちょっとひどいですね...
以下のような内容になります。
- オニオンアーキテクチャ を実践すると、成果物もさることながら プロセスが良かった
- オニオンアーキテクチャには、TDD以外にも様々な フィードバックループ があり、アプリケーション開発に安心・安全とコードの品質向上をもたらすように思えた
オニオンアーキテクチャ
解消できない技術的負債 や アンタッチャブルなレガシーコード で消耗してますか?
頑張って 立派なアーキテクチャ を採用したのに、 実践で問題 がありますか?
オニオンアーキテクチャ はその名の通り ソフトウェアアーキテクチャ の一つで、 クリーンアーキテクチャ のように、コード全体をどう整理するかという指針を示すものであり、 Flux のように、方法論ではなく設計思想が核にある類のものでもあります。
以下のような特徴があります:
- インフラストラクチャの選択肢や、バックエンド/フロントエンドの別に関わらず適用できる
- インターフェースを積極的に活用 し、静的型付け、および DI / IoC との相性がよい
- ビジネスロジックの変化とインフラストラクチャの変化の分離を促進 する
歴史は長い 1 ので、 成果物に関する解説 は既にすばらしい or わかりやすいものがたくさんあります。
- 解説の例:
- エッセンスはこんな感じ:
実践プロセスの問題
オニオンアーキテクチャは、一昔前にDDDが一瞬盛り上がった (?) 頃の話題という印象で、完成形がどうなっているか、という記事はそれなりに発見できます。
しかし、 実践プロセス に関して言及するものは容易には見つけられず、何から始めるか、どうサイクルを回していくか、という知見はあまり出回っていないのではないでしょうか 2 。
実際に仕事で使ってみると、オニオンアーキテクチャは 初期開発から実践する過程にこそ大きな救いがある 、特に複雑なアプリケーション構築の際に役に立つ、ということを実感しました。
テスト駆動開発 は今やよく知られた、フィードバックループを活用して安全に開発を進めるための習慣ですが、オニオンアーキテクチャの実践プロセスには テスト以外の場面にも様々なフィードバックループを設定 する余地があり、アプリケーション構築プロセス全体をより安心・安全で質の高いものに引き上げてくれるポテンシャルがあります。
この記事では、これまでのオニオンアーキテクチャ実践で感じた利点を、Step by Stepで追う開発事例とともに言語化してみたいと思います。
プロセスとしてのオニオンアーキテクチャ
今回、 画像認識を雰囲気で使っていくSlack Bot の開発をサンプルとして扱っていきます。
このアプリケーション自体は複雑なものではないですが、画像認識やチャットボット連携は成長中の分野だけあって、I/Oインターフェースが熟れていなかったり、扱いにくい副作用があったりするので、純粋なビジネスロジックとの共存を考えるのに良いテーマだと思いました。
以下では、このアプリケーションの起案から実用化までステップを追いながら、オニオンアーキテクチャがどう開発をサポートしてくれるか見ていこうと思います。
尚、今回作ったBotのソースコードは、全て以下に公開されています。
Step 1. 要件の詳細化
まずは、特にオニオンアーキテクチャと関係のない普通の活動として、やりたい事を、 ユースケース レベルで詳細化してみます。
オニオンアーキテクチャではコーディングの初期から、実際のユーザのユースケースを明確にイメージする必要があります。
まずはどんな表現でもよいので、形にしてみます。
事例
Slackに画像を共有すると、通信が遅くて画像が見れない人のために何が写っているか教えてくれる よくあるAIクソアプリ Botをつくります。
とりあえずこれを、 Image File Spoiler と名付けてみましょう。 Botが参加しているchannelのみで有効になる想定で、画像のようなシーンの実現を目指します。
Step 2. インフラ設計・技術検証
普通の活動その2。
求められるユースケースがどんな技術を利用して実現できるか、丁寧に検証します。
オニオンアーキテクチャ自体は、 「どんなにインフラ設計をこねくり回しても、ソフトウェアの価値は人間が担保しなければ風化するか形骸化する 」という課題に対するものですが、一方どんなにビジネスロジックやUXをこねくり回しても、 技術的にムリがあるものは成立しないか、続きません 。
事例
この記事の主題ではないため、簡単にまとめます。
Slackのくだりは実現可能なのか?
- Botのいるchannelで画像に反応できるか?
- Events API をBot Eventsで、
file_shared
イベントを対象に有効化すると良さそう
- Events API をBot Eventsで、
- 返事を返すのは?
- 画像ファイルにコメント、が今回のユースケースには合いそう
- Web API の
files.comments.add
を使う感じがする
Botのサーバは?
- 立てたくないので、AWSのAPI Gateway + Lambdaにしておく
- Slackへのレスポンスを同期的に返すのは厳しそうなので、API GatewayとLambdaの間にはLambdaを呼ぶだけのStep Functionsステートマシン実行を挟んでおく 3
画像の中身を言い当てるのは?
- Amazon Rekognitionの DetectLabels API を使っておく 4
言語
言語も重要な技術要素の一つです。
今回は、 TypeScript を選択します。
オニオンアーキテクチャではDIとインターフェースを重用するため、 型の表現力が豊かな程、受けられる恩恵は大きい ように思います。
TypeScriptには、インターフェースはもちろん、null安全なコンパイルオプション、 Promise
による副作用の明示等、効果的で安全なDIをやっていくための資質が十分に備わっています。
ここではなるべく、 使い捨てコード 等も使って、これらの技術的な仕組みが実際に成立するかどうかを検証し切ってしまいます 5 。
Step 3. ユースケースの設計・記述
いよいよコーディングに突入します。
ここでは、実際に ユースケースをソフトウェアに まで落とし込みます。
オニオンアーキテクチャでは、 インターフェースのみを用いてドメインモデルを記述 し、このモデルを使って実現したい ユースケースの場面 を表現します。
どんなモデリングをしたいかによってコードでの表現の仕方は自由ですが、 ...Table
とか <何らかのライブラリ名>Service
のようなインフラストラクチャに依存した概念を盛り込まないよう、気をつけます 6 。
概念設計とコードライティング (=インターフェース定義) を同時にやってもよいですが、ここで重要なのは インターフェースが「書けないな」と思ったら設計を直す ことです。これがオニオンアーキテクチャ実践の 最初のフィードバックループ になります。
事例
実践ではまず、どのようにユースケースの場面を表現するかイメージする必要があります。
名前を付けてみる のが出発点になるでしょう。今回は、 PublishImageFileSpoilerMessage
、「画像のネタバレメッセージを発行する」というユースケースをメインの活動として考えてみます。
コード以前の作業のやり方は自由ですが、ここでは PublishImageFileSpoilerMessage
という場面を、テキストと図を使って表現してみました。
個人的には、 紙 に、図のような関数型設計風のオブジェクト同士の関係性を手書きで図示してみるのが好きです。あらゆる枠線や矢印が自由に使えるし、モデルがなんだか身近に感じられるような気がします。(ちなみに、図中の波線が途中に差し挟まれた矢印は、副作用のある関係を示しています。)
図とともに、以下のようなインターフェース風のメモも書いたりしながら、コードのイメージを固めていきます。
<<PublishImageFileSpoilerMessage>> #fromNewImageFileEvent(event: NewImageFileEvent): Promise<void> ImageFileSpoilerMessageSink #publishMessage(spec: PublishMessageSpec): Promise<void> ImageRecognizer #findObjects(img: ImageFileRef): Promise<ObjectLabels> ...
ここでのシナリオは、以下のようなものです:
- 新しい画像ファイルの発見は、
NewImageFileEventProvider
がNewImageFileEvent
として提供する: イベントからは画像のバイナリを取得する方法の記載されたImageFileRef
が得られる - ユースケース
PublishImageFileSpoilerMessage
では、ImageFileRef
から画像認識器ImageRecognizer
を用いてObjectLabelsInImage
を取得する PublishImageFileSpoilerMessage
は最後に、ラベルを用いてImageFileSpoilerMessage
を作成し、ImageFileSpoilerMessageSink
に投入する
インターフェースをコードに落とし込んだ結果、以下のようになりました (少し単純化しています) 。
図のモデルからは、少し修正を加えた形になっています。
// 画像関連 interface NewImageFileEventProvider { get(): Promise<NewImageFileEvent>; } interface NewImageFileEvent { getImageFileRef(): Promise<ImageFileRef>; } interface ImageFileRef { getContent(): Promise<NodeJS.ReadableStream>; } // 画像認識 interface ImageRecognizer { findObjects(imageFileRef: ImageFileRef): Promise<ObjectLabelsInImage>; } interface ObjectLabelsInImage { labelsWithConfidence: ReadonlyMap<string, number>; } // メッセージ送信 interface ImageFileSpoilerMessage { mesage: string; } interface ImageFileSpoilerMessageSink { publish(message: ImageFileSpoilerMessage): Promise<void>; } // ユースケース全体 class PublishImageFileSpoilerMessage { public invole(): Promise<void> { // 上記の役割群を利用してユースケースを表現する (今はしなくてよい) return Promise.resolve(); } }
この段階でユースケースの中身の実装まで行う必要はありません。ここでは自由奔放に作った ユースケースのモデルが、ソースコードの世界のインターフェースにマッピングできるかどうか だけ検証できればよいのです。
オニオンアーキテクチャでは、ここで記述するものをインフラに依存しすぎない形で変更できるのが1つの利点で、DDD的なドメイン知識の表現や関数型設計など、やりたい放題できるのが楽しいところです。
Step 4. ユースケースのテストの記述
今の所、このアプリケーションは絵に描いた餅 (=インターフェースだけの存在) ですが、ここで テスト を書きます!
このStepでの 目的は2つあり、それぞれがフィードバックループを形成 します。
実施するのは一般的なTDDですが、これらのフィードバックループを適切に運用するため、以下の点に留意します。
テストの対象はユースケース
テストコードを記述する対象は、いわゆる "ユニットテスト" の対象となるようなオブジェクトではなく、ユースケースのインターフェースです。多くのアプリケーションでは、 ただ一つのオブジェクト に対するテストを記述することになります。
外部設定や外部依存関係、その他 アプリケーションコードだけではコントロールできない副作用を持つ全てのオブジェクトのモック を作成し、 唯一の実体を持つオブジェクトであるユースケースのテストを記述・実施 します。
モックの対象となるオブジェクトは通常、 単発のユースケースよりも生存期間が長く、いつも暗黙的にただそこにあってアプリケーションに特定の役割を提供するようなもの です。ファイルシステム、データベース、ネットワーク、外部サービスの類が関連するものは全てこれに該当します。個人的にはそれらを "ドメインの役割" と呼んで、その他のオブジェクトと区別しています。
ここでもやはり、 「テストが書けないな」と思ったら、Step 3. のモデリングを修正 します。 「テストケースが書けないな」も「モックが書けないな」も等しく修正対象 となります。これが全体から見て 2つ目のフィードバックループ です。
ユースケースのテスト駆動実装
テストの記述さえできれば、あとは Red-Green-Refactor のプロセスに従ってテストを進め、ユースケースの内部の実装をやり切るだけとなります。
TDDに慣れている開発者からすればお馴染みの感覚かもしれませんが、テストケースとの対話を通じて、ぼやけたイメージしかないユースケースの詳細仕様を固め、また実装していきます。これは 3つ目のフィードバックループ となります。
事例
まずは、テストで実際にユースケースを動かすにあたり必要となる、DI機構の整備を行います。
今回は、 InversifyJS を利用しました。
DI/IoCコンテナは生成関数を定義すると、テスト等での取り回しがしやすくなります。
import { Container } from "inversify"; export const buildServiceRegistry = (): Container => { const registry = new Container({ defaultScope: "Singleton" }); // 様々な実装の注入 ... return registry; };
また、自動テスト機構の整備を行います。
今回は、AVA を利用しました。
クリーンで好きな選択肢ですが、オニオンアーキテクチャのテストは基本的に複雑にはならないので、シンプルな機能で足りるという意味でもAVAにしています。
ユースケースのテストに向けて、 "ドメインの役割" のモックを作成していきます。
インターフェースの契約を満たすもっとも単純な形にしますが、もしテストのためにSpyのような機能が必要であれば加えます。そういった機能が必要にも関わらず、テストで使えるモックが作れないのであれば、インターフェースを見直したほうがよいでしょう。
// 単純なケース const createNewImageFileEventProviderMock = ( event: NewImageFileEvent ): NewImageFileEventProvider => { return { get: () => Promise.resolve(event) }; }; // Spyのような機能を持つパターン // * 後から `sink` を検査し、 // * `publish` された `ImageFileSpoilerMessage` を追跡する const createImageFileSpoilerMessageSinkMock = ( sink: ImageFileSpoilerMessage[] ): ImageFileSpoilerMessageSink => { return { publish: message => { sink.push(message); return Promise.resolve(); } }; };
あらゆる "ドメインの役割" をモック化したところ、ユースケースのテストには 画像から検出したラベルのパターンに対してどんなメッセージを発行するか というロジックだけが残りました。
import { Container } from "inversify"; import { test } from "ava"; // ユースケースのテスト実行関数 const invokeUsecase = ( registry: Container, // DIコンテナ recognizerResult: ObjectLabelsInImage ): Promise<void> => { registry.bind<ImageRecognizer>(ROLES.ImageRecognizer) .toConstantValue(mocks.createImageRecognizerMock(recognizerResult)); return registry .get<PublishImageFileSpoilerMessage>(ROLES.PublishImageFileSpoilerMessage) .invoke(); }; // 典型的なテストケース // * ※ AVAのTest Contextには、 // * 毎回モックと共に初期化されたDIコンテナが差し込まれるよう小細工をしています test(">=90%のラベル1つを検知", t => { const labels: [string, number][] = [["dog", 1.0]]; const recognizerResult: ObjectLabelsInImage = { /* labelsを用いて結果を構成 */ }; return invokeUsecase(t.context.registry, recognizerResult).then(() => { t.is(t.context.messagesSent.length, 1); t.deepEqual(t.context.messagesSent[0].formalExpr, "certainly(dog)"); }); });
一応いくつかの "ドメインの役割" の中をデータが流れては行きますが、テストが検証するのは結果的に、何ら副作用を持たないメッセージ構築のルールのみとなりました。
ユースケーステストで検証されるのは普通、アーキテクチャに依存しない部分のうち、実務上もっとも面倒で、もっともソフトウェアの挙動にとって重要なものになります。これはいわゆる "ビジネスロジック" と呼ばれるものですが、外部依存関係を全て排除し、大部分の知識を契約のインターフェースに委ねた後に見えてくるのは、案外このように単純な宣言的定義だけだったりするかも知れません。
最後に、テストが通るよう、モックのままの "ドメインの役割" を使ってユースケースを実装します。
ユースケースに必要な役割を全てInversifyJSの機構によって注入し、これらを用いて仕様を表現します。
最終的には、おおよそ以下のような形になりました。完全なコードは こちら になります。
// 新しい画像ファイルへの参照を取得し、 const newImageFileEvent = await this.newImageFileEventProvider.get(); newImageFileEvent.getImageFileRef() .then(imageFileRef => // 画像認識器からラベルを取得し、 this.imageRecognizer.findObjects(imageFileRef) ).then(objectLabelsInImage => // ラベルの情報からメッセージを作成し、 this.composeImageFileSpoilerMessage(newImageFileEvent, objectLabelsInImage) ).then(message => // 投稿する this.imageFileSpoilerMessageSink.publish(message); );
ここまでで、外部依存関係を含まず、どんな環境でも実行できるテストが用意できました。これは、ビルド時間を除けばわずか数秒で、ビジネスロジックの包括的な検証を行えるものです。
次のStepではこれを、アプリケーションとして動かしていきます。
モックを使ったテストでは、不安があるでしょうか?
オニオンアーキテクチャでは、ユニット・コンポーネントレベルの動作が (ユースケースとの間の) インターフェースによる契約関係によって保証されています。
ここで検証するのは、契約に従って記述されたユースケースが正しく動作するか、あるいは契約そのものの整合性であって、個々のユニット (ドメインの役割) が契約に従っているかどうかではない という点が重要です。
個々の "ドメインの役割" が契約を守って動作するかどうかは、実際のインフラストラクチャと接続された状態で検証しなければ意味がありません。外部サービスやDBをテストしようとするなかれ、とはTDDの金言ですが、オニオンアーキテクチャではこの警告に対して、具体的な指針を提供します。
もちろん、よほどの余裕があるか、よほど複雑な役割の実装があるのであれば、個別の実装ごとにテストする (e.g. Dockerでテスト用DBを準備する前提でのユニットテスト) のも良いでしょう。しかし、ユースケースとの契約はユースケースの発展に伴って変更される場合があることを、心に留めておく必要があります。
Step 5. アプリケーションの作成とドメインの役割の実装
テストが終わったら、いよいよユースケースを起動/実行するための アプリケーションを、技術要件に従って実装 していきます。これに伴い、モックのみの状態だった "ドメインの役割" を、実際のインフラストラクチャ上で動くように記述 していきます。
このStepは、利用するインフラに対する知識や検証の成果を使って 最適な処理 を記述する 最も純粋に技術的で泥臭いフェーズ ですが、ユースケースとの契約さえ破らなければ、実際には公開インターフェースを守って雑にコードを書くだけ、という作業になります。各 "ドメインの役割" に求められる入出力の契約は型によって守られているので、 コードのクオリティは気にしすぎない で進めていきます。
Step 5.では明確にフィードバックループがある訳ではないですが、ユースケースがアプリケーション統合しにくい形だったりした場合に修正することがあるかも知れません。
ちなみに、実際の外部サービスやインフラとの接続のため、設定注入機構やロガーを用意するのもこの段階になります。
事例
PaaS等で動かすアプリケーションの場合、先にローカルマシンで動かせる形のアプリケーションを作って動くようにすると便利です。アプリケーションの実際の動作を安全に検証できるだけでなく、「インフラ構成や設定のせいかと思ったら実装コードがまずかった」という類の無駄なつまずきの予防になります。
オニオンアーキテクチャでは、簡単な役割の実装やアプリケーションのエントリーポイントを差し替えるだけで、こういったものを素早く制作できるのも利点の一つのように思います。
ということで、まずは、 ターミナルの対話入力で画像ファイルの絶対パスを読み取り、実実装を使って結果を発行するCLIアプリケーション を作ってみます。
以下のオブジェクトを実装しました:
- 本番でも使う役割の実装
- CLIアプリケーション用の役割の実装
- コマンドライン入力を待ち続ける
NewImageFileEventProvider
- コマンドライン入力を待ち続ける
エントリーポイントの処理の流れは、外部環境に接しているアプリケーションインターフェース (ここではCLI) に合わせ、 適切な "ドメインの役割" をアダプタとして用意 して注入 、あとはユースケースを起動するだけ、という形になります。
以下のような実装になりました。完全なコードは こちら になります。
// DIコンテナのルートを取得 const serviceRegistry = buildServiceRegistry(); // コマンド入力待機型のアダプタ(= `NewImageFileEventProvider` のCLI適応系)を作成して注入 serviceRegistry .bind<NewImageFileEventProvider(ROLES.NewImageFileEventProvider) .to(CliNewImageFileEventProvider); // 同様に、 `ImageFileSpoilerMessageSink` を適応・注入 const messageLogger: ImageFileSpoilerMessageSink = { publish: message => { console.log(message); return Promise.resolve(); } }; serviceRegistry .bind<ImageFileSpoilerMessageSink>(ROLES.ImageFileSpoilerMessageSink) .toConstantValue(messageLogger); // ユースケースを取得し、起動する const usecase = serviceRegistry .get<PublishImageFileSpoilerMessage>(ROLES.PublishImageFileSpoilerMessage); usecase.serve();
動作させてみましょう。
結果出力以外のログが少し鬱陶しいですが、動きましたね!
このアプリケーションでは、CLI実装を NewImageFileEventProvider
の 実装クラス に委ねています。
CLIはプレゼンテーションの一種と言えますが、オニオンアーキテクチャでは プレゼンテーションはインフラストラクチャの一部 であり、基本的に "ドメインの役割" の一部として実装されます。オニオンアーキテクチャはこの点が曖昧になると、ただの 手の混んだレイヤードアーキテクチャ になりがちなので注意しておきます 8 。
CLIアプリケーションが想定通り動作することを確認できたら、 AWS Lambda上で動作するアプリケーション を作成します。
プロダクション用の役割の実装を行いますが、既にユースケースの中心部の実装は終わっているので、 インフラストラクチャアダプタを作って注入 するだけ、という形になります。
入力アダプタ は、AWS Lambda関数に入力されたSlackのイベントペイロードに応じてオンデマンドで作る必要があります。こちらは、InversifyJSの Container#resolve
(依存関係の注入が定義されたクラスをその場で解決する機能) を用いて ファクトリ を取得、生成してみました。
最終的なAWS Lambda関数ハンドラは、以下のようになりました。完全なコードは こちら になります。
export const handler: AWSLambda.Handler = async (event, _context, callback) => { // DIコンテナのルートを取得 const serviceRegistry = buildServiceRegistry(); return serviceRegistry .resolve(SlackImageFileEventProviderFactory).getProvider(event) .then(({ provider, slackNewFileEvent }) => { // AWS Lambdaハンドラ型のアダプタ // (= `NewImageFileEventProvider` のLambda起動イベント(+Slack Event)適応系)を作成して注入 serviceRegistry .bind<NewImageFileEventProvider>(ROLES.NewImageFileEventProvider) .toConstantValue(provider); // 同様に、 `ImageFileSpoilerMessageSink` を適応・注入 const imageFileSpoilerMessageSink = serviceRegistry .resolve(SlackImageFileCommentatorFactory) .createSlackImageFileCommentator(slackNewFileEvent.event.file_id); serviceRegistry .bind<ImageFileSpoilerMessageSink>(ROLES.ImageFileSpoilerMessageSink) .toConstantValue(imageFileSpoilerMessageSink); // ユースケースを取得し、起動する const usecase = serviceRegistry.get<PublishImageFileSpoilerMessage>( ROLES.PublishImageFileSpoilerMessage ); return usecase.invoke(); }) .then( () => callback(null), err => callback(null, err) ); };
動かしてみると…
AWS Lambdaを用いたSlack統合アプリケーションが動いている様子
問題なさそうですね! 9
完成!そして今後の変更にむけて
今回のアプリケーションの実装はこれで完了ですが、オニオンアーキテクチャでは、以後のコードの変更の際も、同じようなStepを辿ってコードの健康状態を維持していきます。
ユースケース・ビジネスロジックに変更があった場合は、基本的に今回のStep全体をもう一度なぞる形になります。
変更内容に応じて、ユースケース、モデルインターフェースとテストの修正を行います。静的型付けの言語で実装を行った場合、インターフェースで表現された "ドメインの役割" 等の契約が変更されると必要な変更点がコンパイルエラーとしてリストアップされるので、これに従って修正を実施できます。
また、場合によっては "ドメインの役割" を新たに追加する必要がある場合もあるかもしれません。
あるいは、インフラストラクチャに対する要求が変化した場合、まずは技術検証フェーズを経て、変更後の構成やデータ移行の構成を固める必要があるでしょう。
その後、 "ドメインの役割" の実装を新しいインフラストラクチャの特性に合うよう書き換えるか、または丸ごと新しい実装に差し替えることができます。
インターフェースによって交わされた、ユースケースとの小さな契約を守りさえすれば、実装の提供の仕方には自由があります 10 。
ところで、いずれの場合でも、開発者にはアプリケーションのユースケース、および "ドメインの役割" の契約に対するそれなりの理解が求められます。オニオンアーキテクチャでは、ソフトウェアに それを理解しなければ容易には変更できない制約 を付加します。
これが望ましい性質かどうかは、プロジェクトの種類や規模、存続期間に依りますが、少なくとも品質に対する要求が高いソフトウェアでは、目的の達成に貢献してくれる可能性が高いと思います。
考えられる論点
実践上の補足をいくつか、追記します。
時間はかからないのか?
実装フェーズのみを "やらない場合" 11 と比較すると、もちろん若干の追加時間を要し、体感では最大1.5倍程になるように思います。
ただ、個々のStepで目的として設定された検証を確実に行い、前のStepに対するフィードバックが機能すれば、全体の手戻りはかなり少なくなるはずです。
もともと、数週間〜数ヶ月で捨ててしまうようなソフトウェアの開発には向かないので、対象のプロジェクトや実践のスコープは適切なものを選ぶ必要があります。
これはウォータフォールではないのか?
巨大な開発プロジェクト全体でこれらのStepを進めていくようであれば、そうなってしまう可能性はあります。一方、そういったスコープの切り方はそもそも適切ではありません。
あくまでユースケース (ログインするだけ、会員登録するだけ、商品をカートに入れるだけ、等) の単位で、局所的に整合性のあるモデルを作って開発に役立てるのがオニオンアーキテクチャの核となります。
その後、モデルをどのくらい多くのユースケースで横断して利用できるものにするかは、マイクロサービス分割等と同じように、別途判断を要するものです。
まとめ
ここまでで、オニオンアーキテクチャを実際に始める際の手順を通して、この方法がどのように安心・安全で高品質な開発に貢献してくれるか見てきました。
人による部分はあると思いますが、ソフトウェア開発の中で単純作業や流れ作業が続くような場合、個人的にはとてもやる気が削がれます。
オニオンアーキテクチャでは、プロジェクトを始めるとあらゆるステップに設計とフィードバックループが含まれ、開発中は常に、ソフトウェアの向上に貢献する知的な作業に時間を費やすことができます。
冒頭でも述べましたが、オニオンアーキテクチャは上手く使えばこういった意味でも、完成形だけでなくプロセスで大いに価値を発揮できるアーキテクチャだと思います。
実際の開発では何よりもスピードを求められることの多い時代ですが、自分の「理想的な手順や形」のイメージがあると、日々の細かい作業の最適化やアーキテクチャの行き詰まりの問題に直面しても視界が保て、生産的でいられる助けになるように思います。
それは自分にとって今の所、オニオンアーキテクチャや関数型です。
-
自分が オニオンアーキテクチャの電波 を初めてちゃんと感じ取ったのは、所謂「DDD実践本」だったかと思います。調べる限りでは、はじめに提唱したのは Jeffrey Palermo氏 (2008) という方なのでしょうか (? “自分は名前を付けただけだ” とのことではあるが)。ちなみに、DDDと関わりが深いのは確かですが、少なくともDDDの文化的側面を抜きにしても実践できるアーキテクチャです。↩
-
これは XXXアーキテクチャ 全般に言えることかもしれませんね… とはいえこの記事のように、書いてみると際限なく長くなってしまうのであまりやられないのかもしれません..↩
-
TensorFlow.js のNode.jsバインディングを使いたいと思っていたのですが、記事を書き始めた頃はまだ開発中でした…↩
-
少し手順が重いようにも思えますが、もし「お試しコード」やプロトタイピングを作成する場合はこの段階の活動でプロジェクトを終え、以降のStepに進む必要もないと考えます。↩
-
技術検証のことはいったん全て忘れます。おおよそのインフラ構成が頭の片隅にありさえすれば、ここでどれほど自由に設計をしても、アプリケーションが破綻することはないように思います。またインフラは蔑ろにされる訳ではなく、後に外部依存関係の分離をレビューするStepや、集中的な実装を行うStepがあります。↩
-
次のStepで導入するDIためのコードが既に少し混入しています。↩
-
とはいえ、例えばWebアプリケーションフレームワークのようなものに接続する場合、全てのメソッドを役割としてユースケースに注入するのはあまり現実的ではないでしょう。ユースケースの実装は “ポータブル” なので、ユースケースを “サービス” のようにしてメソッドハンドラ内で使い回すことができます。どのような “サービス” 分割、メソッド分割にするかは自由ですが、この場合も、CLI等の別のプレゼンテーション方式では “ドメインの役割” として注入されるべきものがユースケースの外に漏れ出ないよう、注意しておく必要があります。↩
-
デプロイやその他、インフラストラクチャを整える作業がここで発生するはずですが、この部分はオニオンアーキテクチャのスコープ外なので省略します。↩
-
と、言葉で記述しても説得力に欠けますね… 元々やりたかったTensorFlow.jsの組み込みや、Amazon Rekognitionの画像サイズ制限に対応するための修正を、別途実施してまとめてみたいところ。↩
-
厳密に、それはどんな場合?と言われると難しいのですが..↩
リズムゲームを作るのに必要っぽいこと(準備編)
この記事はDark - Developers at Real Kommunity Advent Calendar 2015の22日目です。
寝るまではまだ22日目なんだ…!
って書いてからその認識さえ一日遅れていることに気づきました。本当です。
このカレンダーも終わりが近づいて来ましたね…!
みなさん、音ゲーやってますか? 僕は某フェスと某ステージのイベントがたまに被ったりして大忙しです。
こういったリズムゲームをやっていると、たまに以下のような不満を感じることがあります。
- 好きな曲なのにリズムパターンやノートの置き方が気に食わなくていまいち盛り上がれない
- もう少し難しくてもいいのにと思う
- 逆に難しすぎだろアホと思う
ざっくり言ってしまえば、「なんかリズムが気に入らないな」という話。
リズムゲームのパターンの作り方はゲームによっても曲によっても様々で、分かりやすい所ではパターンがドラムスに寄っているか旋律に寄っているか、そのゲーム特有の特殊な打ち方(押し続けやフリックなど)をどの程度用いるかというような違いがあります。
このパターンが自分の好みに合わない場合、とくに好きな曲をプレイしていると、とても残念な気持ちになってしまうことがあります。
自分で作る
もしそんな不満を抱えているあなたがエンジニアなら、気に入らないものは自分で作ってしまいましょう!
この記事では、リズムゲームを作るのに必要そうな技術的課題以外の、準備の部分を中心に取り組んでみました。
もちろん僕はリズムゲームを作るプロではないので、本場の方法と大きく異なる部分があるかもしれませんんが、一応そろえれば作れるよ、というものとして書いています。
この記事を見てもおもしろくなさそうな方:
- 今あるリズムゲームに特に不満がないか、リズムゲームに興味のない方
- 既にリズムゲームを自分で作れる、仕事で作っているという方 (→ 生暖かい目で見守ってください)
- DTMをやっているエンジニア (→ たぶんすべて知っている内容です)
- リズムゲームの演出とか運用とかについて知りたい方 (→ 僕も知らないっす)
こんな手順になります。
音源にあわせてリズムパターンをつくり、最終的に音源の再生時間基準でどこをタップするか、みたいな情報を含んだデータに落としこむのが基本的な流れ。
何かアプリケーションを作るところまでできれば良かったんですが、打ち込みに3時間くらいかかって力尽きました……
ちなみにこのうち、*
の付いている手順は、少なくともサービスとしてまっとうな方法でリズムゲームを作る場合必要のない手順です。
1. 音源を用意する
何はなくとも、曲の音源です。
今あるリズムゲームに不満がある方は、実際のリズムゲームから吸い出すのもよいかもしれません。 某ステージには自分のプレイ音が混じらないMVというモードがあるので余裕ですね! (こうして吸いだした音源を他人に頒布したり自分で楽しむ以外の諸々の用途に供するのはいけないことなのでやめましょう!!あと曲を聞きたいなら買おう)
自分で用意する場合も、1曲のプレイ時間として適当な(あと打ち込みで死なない)長さに気を付けます。
2. DAW的なものを用意する
DAW = Digital Audio Workstation
です。
実際のところ、MIDIと音声ファイル等を同時に読込み・編集・書出しできるものならなんでもOKですが、こういうのはDAW
で検索するのが一番でてきやすいです。
ちなみにMIDIは今のところ、いつどんな音が鳴るかを書き込める電子的な楽譜みたいなものと思っておけばとりあえずよいでしょう。
今はフリーでもちゃんと使えるものがたくさん出ていて最高ですね…昔の無料シーケンサーなんて...うっ‥頭が…
今回はTracktionを使いました。少し昔のバージョン(Tracktion 4)が無償で提供されています。
3. 曲のテンポを計測する
今どきの音楽でDAW的なものを介さずに制作されているものは恐らくないと思われ、その場合、打ち込みの部分と生演奏・録音の同期をとるために、必ず曲全体が精確なテンポに従ったタイムラインの上で管理されているはずです。
しかしながら、今回のようにひとの曲を勝手に使ってどうこうしようという場合、自力でテンポを計測して知る必要があります。
今回使ったのはこちら。非常に典型的なBPM(= beats per minute)測定アプリで、テンポに従ってタップすることでタップ間隔の平均的なテンポを知ることができます。
曲を聞きながら、ひたすら無心でタップしましょう。
リズムゲームで培った精確なタップ力が試されますね。
4. 音源の位置合わせをする
DAWを起動して新しいプロジェクトを作り、計測したテンポを設定したら、用意した曲の音源を適当なトラックにロードします。
音源の内容がトラックにべーっと貼り付けられたら、音源の最初の表拍などをDAWのタイムラインの拍と合うように、音声トラック上を移動します。
プロジェクト全体を再生しながら、録音用のシグナル音みたいなものがあればその音と、なければ適当な音をMIDIトラックに打ち込んで、DAWの拍と音楽の拍が合うようにします。
当然ですが、テンポが合っていない場合は曲と他の音がだんだんズレてきます。 そうでない場合は初めから終わりまで同じ時間のズレが維持されてかなり判定が難しいですが、頑張って心の耳で合わせます。
5. リズムパターンを打ち込む
いよいよリズムパターンの打ち込みです。
自分の魂のビートを打ち込むことで、リズムゲームでしっくり来なかった鬱憤を存分に晴らします。
ただし、この作業は恐ろしく時間がかかります…
ピアノロール(画像)というのはおおよそ人間の使用に耐えるものではなく、プロの人は電子楽器等で演奏したものを自動補正することで打ち込むため、今後もまともな進化は期待できない[要出典]UIです。
気に食わないリズムパターンを叩くよりここでよっぽどフラストレーションがたまっている気がしますが、まあ気にしないことにします。
タイミングのデータを作っているだけなので楽器を選ぶ意味はありませんが、ここでトラックや音の高さなど打ち分けておくと、後 の処理で参照して何かの目印に使うことができます。
6. MIDIファイルと音源を吐き出す
打ち込みが終わったら、打ち込んだリズムパターンの部分をMIDIファイルにして出力します。
また別に、打ち込んだパターンをミュートにして音源の部分も出力します。これによって、位置合わせした分の空白が反映された新しい音源を作ります。
7. MIDIファイルの中身を変換する
ここに来てやっとエンジニアらしい作業です。
MIDIファイルから情報を抜き出し、曲の音声ファイルの特定の再生時間でどこをタップしてください、みたいなデータを作ります。
MIDIファイルを扱うためのライブラリはきっと言語ごとに一つくらいはあると思うので、そういうのを使います。今回はPython製の以下を使いました。
こんな感じでダイレクトにファイルを読むインターフェイスを用意してくれているので、すぐに本来の作業に取りかかれますね。
import iomidi song = iomidi.read('midifile.mid')
MIDIの構造
MIDIに変換をかますには、MIDIファイルの構造を知る必要があるでしょう。
iomidi
のオブジェクトのプロパティを参考にざっくり見てみると、以下のようになっています(もちろん実際はJSONではありません)。
// song { header: { trackCount: 5, division: 960, frmt: 1 }, tracks: [ trackObject, trackobject, ... ] }
ファイル全体の情報が書かれたヘッダに、独立したトラックがいくつか含まれていることがわかります。
ヘッダで重要なのはdivision
です。ファイルの中の時間は全てtick
と呼ばれる単位の整数倍で表されていますが、このtick
の分解能を表すのがこの値で、tick
が1秒の何分の一かを示しています。
ここでは960なので、おおよそ1tickは1msくらいになります。
トラックは先に打ち込みで作った数+1だけ存在するはず(トラック0はなんかシステム的なやつが入っています)。トラックの中身を見てみます。
// song.tracks[1] { events: [ eventObject, eventObject, ... ] }
うん、もっと中を見ろってことですね。
event
の中身はこんな感じです。
// song.tracks[0][N] { NoteOnEvent: { velocity: 96, channel: 0, key: 50, delta: 1650 } }
案外シンプル(?)ですね。
理解しやすいものの定義はこんな感じです。
key
: 打ち込みで入れた音の高さ。ドラムとかにもちゃんとついている。
ごく普通の音を鳴らすイベントは、基本的に音を鳴らし始めるNoteOnEvent
と鳴らし終えるNoteOffEvent
の合わせ技でできていて、同じkey
でNoteOffEvent
が発生すると前回のNoteOnEvent
の音が止まる感じになります。
velocity
: 速さ…ではなく音の強さを言います。今回の処理には関係なし。
channel
: MIDIのしくみ系の値。今回の処理には関係なし。
delta
について
MIDIはそもそも、電子楽器などの演奏データを楽器や機器の間でリアルタイムに交換するための信号の規格で、MIDIファイルの中身も少し変わった形で記述されています。
delta
はその事情を反映した値になっている気がします。
delta
は、そのトラックの前回のイベントから自分までの時間差をtick
で表した値です。
今回欲しいのは音源のどの再生時刻にタップが入るかなので、トラックの最初のdelta
から順次足し合わせる必要があります。
なんかこんな雰囲気ですね。
def to_tap_sequence(midi_track): accumulated_time_ticks = 0 for event in midi_track.events: accumulated_time_ticks += event.delta yield dict( tap_position=select_position(event), time_ticks=accumulated_time_ticks)
このdelta
さえなんとかすれば、いい感じのデータが作れそうな感じがします。
必要に応じて、NoteOnEvent
だけを選出する必要はあるかもしれません。
あとは時間単位をミリ秒等になおして、
import decimal def tap_sequence_with_ms(seq, midi_header): for event in seq: event['time_ms'] = ticks_to_ms( event['time_ticks'], midi_header.division) yield event def ticks_to_ms(ticks, time_division): return float( # a tick as milliseconds 1 / decimal.Decimal(time_division) * 1000 # multiplied by #ticks * ticks)
必要に応じてトラックのイベントをマージすれば、
def merge_tap_sequences(seq_a, seq_b): # 効率悪.. rev_seq_a = list(reversed(list(seq_a))) rev_seq_b = list(reversed(list(seq_b))) while rev_seq_a and rev_seq_b: if rev_seq_a[-1]['time_ticks'] <= rev_seq_b[-1]['time_ticks']: yield rev_seq_a.pop() else: yield rev_seq_b.pop() while rev_seq_a: yield rev_seq_a.pop() while rev_seq_b: yield rev_seq_b.pop()
なんかできた気がする!
こちらがここまでで作ったものになります。
一応CLIが付いていて、なんか結果を吐いて音源と比較してみるとどうもtime_ms
の時間がおかしい気がするんですが、まあまだこの先を作っていないので仕方なし…とりあえずデータの作り方のコンセプトは合っているはずです。
あとは、作りたいゲームの特性に応じて情報を作ってイベントにくっつけくっつけしていけば、データは完成するのではないでしょうか(適当)。
さあ、ゲームらしきものを作る段には全然入ってませんが、ここまでのデータがそろっていれば、エンジニアならあとは何となく作れる気がして来たのではないでしょうか(自分も年末にやり切りたいです…いつかゲーム実装編が書かれる…かどうかはわからない)。
音楽ゲーム系のアプリケーションは何となく難しそうなイメージがありますが、本当に難しいのは気持ちよくプレイするためのタイムラグとか処理落ちをなくすとかの部分で、DAWをさわれてMIDIの知識をつければ、案外その前までは作れてしまうものです。
これを読んで頂けたエンジニアの人は、興味があったら是非作ってみて下さい!
そしてできたら僕にも遊ばせてください!
次は@sinamon129です!
あれ…もう書かれているぞ…?!おかしいな…(おかしくない)
【ポエムと】闇に飲まれるな【宣伝】
この記事はDark - Developers at Real Kommunity Advent Calendar 2015の15日目です。
Classic ASPの残念な話をネタで書こうと思っていたんですが、書いていたらあまりにつらくなってしまって無理だったので替わりのポエムです。
特に酔って書いたわけではないので、逆にちゃんとポエムできていないかもしれませんが、ご容赦下さい。(文章が長くてわかりにくいのはデフォルトです)
このコミュニティのターゲット、「(だいたい)新卒」な皆様の中には、きっとそろそろ自分がエンジニアと言う職種の暗闇の部分に片足を踏み入れているように感じている方がいるのではないかと思います。
そんな人に向けてのお話です。
読んでも得しなそうな方
- 仕事が楽しい方 (→ 意味もなく暗い気持ちになるかもしれません)
- エンジニアとしての自分の成長について常に危機感をもって行動している方 (→ 気分を害するかも知れません)
- 死にたいほどつらい方 (→ 病院へ)
ソフトウェアエンジニアは、生きながらにして死んでしまうことがあるように思います。
本当に命を削って仕事をされている方には申し訳のないことですが、ここで言っているのは、
それなりの健康を維持できる環境で働いている中でも以下の様な状態に陥って、エンジニアとしてゆっくりと死んでいくという話です。
- 開発することが楽しくなくなる
- 仕事上の問題解決はソツなくできるので今のままで別にいいような気がする
- 組織運用上の問題は無視するか、技術で力ずくの解決をしようとする
誰しも疲労が蓄積すると好きなことも疎かになってしまうものですが、ゆっくり死んでいくタイプのエンジニアはたまにはちゃんとゆっくりできるのが普通なので、問題はむしろ別のところにあるように思います。
より良い仕事の成果を自然に目指していけるという希望を失うこと
多くのエンジニアは、開発の仕事さえ与えておけばずっと生きている虫のような生きものとは違うので、エンジニアを目指した時の気持ちを再確認する機会が目減りしてしまったとき、誇りを失い、自分がエンジニアとしてできることが何なのか忘れてしまうものです。
- 古代遺産を大人の事情で守らなければならない
- 政治的な理由で選択できる技術が限られる
- 実現する価値が会社のビジョンと関係ない
説明はしませんが、きっとここまで読み進められた人には分かってもらえるでしょう。
それまで自分のプライドをかけて選んできた技術、方法、名前、そして何に貢献するのか ということ。
こういった気持ちを奪われたエンジニアは、日を追う毎に受け身がちになり、開発する機械のような振舞いで身を守るようになります。
外に目を向ける
ずっと前から何度も言われてきたことですが、会社や仕事の範囲にとどまらず、外に目を向けて客観的に自分の置かれている状況を見つめること。
僕自身、何となく静かに死んでいきそうになっていた頃、@moschanと@arataに運良く誘ってもらうような形でDarkに関わるようになって、イベントを開催して、
この世にはこんな恵まれた環境でエンジニアをやっている人がいるのか、とか、技術を好きな人がこんなに集まってくれるのか、という嬉しい気持ちがあったりした一方で、
自分を取り巻く環境を変えるのは勿論ですが、その後自分の状況とどう向き合っていくべきか、意外とすぐに答えが出るものではありませんでした。
老害について
こういった文脈でよく話題に上がる老害という単語があります。
僕自身はあまり好んで使う言葉ではないですが、一方ソフトウェア開発の世界に限らず、どんな業界・ドメインでもかなり普遍的に見い出される概念のように思います。
この言葉はだいたいいつも様々な負の感情と結びつくのでなかなか捉えづらいものがありますが、共通して彼らに見られるのは、
自分が今まで作り上げ、継続してきたやり方に誇りを持っている
という特徴です。
これは、ゆっくり死んでいく系エンジニアから見るとげに羨ましいことです。
彼らは事実として、上で書いたような僕らの苦しみに関与してはいますが、実はある意味、僕らが渇望する幸せなエンジニアの一つの形を体現しています(いました)。
彼らはちょうど技術者として幸せな時間を過ごせるタイミングをつかんで一時代を築いた結果なのだと思いますが、
重要なのは一時代を築いたことではなく(申し訳ないですが)、
一時代を築くにあたって、誇りを持ってエンジニアリングに対峙し、またその誇りを継承してきた彼らの姿勢にあります。
これはある意味で、エンジニアとしての一つの理想像とも言えないでしょうか。
もし彼らと同じ組織に所属し、同じビジョンを目指しているのならば、彼らが本当はどんな価値観をなぜ大切にしているのかよく知り、どのような視点で会社の目指すものを見つめており、僕らとの間の溝が一体何なのか理解すべきです。
その上で
組織を理解して変えていくためには、どの程度の時間と労力が必要でしょうか。
自分にそれを支払うことはできるのか。他にこの作業を行うべき人は、今何をしているでしょうか。
僕らと彼らの問題がクリアになって解決した時、その先に僕らの目指すものはあるでしょうか。 彼らの視点は尊重するに値するものだったでしょうか。
それらは、本当に今僕らの問題として取り組むべきことなのでしょうか。
誘い
@arataが最近言っているとおりDarkは雑なコミュニティですが、雑だからこそ誰にでも入り込める余地があるし(不審者のご入場はお断りしております)、色々な状況や考えの異なる人たちが集まれる場所になっているように思います。
いろいろな人の話を聞いて、他のひとが目指す価値と自分の目指す価値、他のひとの環境と自分の環境について比較してみてください。
別々の誇りをもった二人がぶつかれば、トシに関わらずきっとそこにROUGAIが生まれます。
今の自分の闇は許容すべきなのか、
あるいは闇を受け容れるとして、状況を打開するためのコストを今かけるべきなのか、
自分は将来、いったいどんなROUGAIになりたいのか、
一緒に考えていきましょう。
こんなアドベントカレンダーを書いても(きっと)平気なコミュニティですので、気軽に遊びにきて下さい。
運営のくせに恩恵を受けてばっかりで闇の話しかしないし、なんかちゃんとエンジニアとして活躍していてコミュニティの話題を作ってくれている人たちには申し訳なさが結構あるんですが、
やっぱり今沈んでいるたくさんのエンジニアに、道をとりもどすきっかけをつかんで欲しくて、このコミュニティはそういう場としてもあり続けられるんじゃないかと思っているのです。
まとめ
- 闇に飲まれているエンジニアの皆様、Darkへ参加しませんか?(たのしいよ!)
- 闇から抜け出した先で得られるものと、闇に今立ち向かって得られるものを天秤にかけよう
- という自戒混じりの宣伝
※ 老害を理解する云々はだいたいこちらの受け売りなので少しでもピンときた人は見てみると良いと思います。
明日はきっと光の話題です!乞うご期待!
机の上にシルクジャスミン
この記事はDark - Developers at Real Kommunity Advent Calendar 2015の11日目です。
こんにちは、Darkでピザ注文職人[要出典]をやっている@ymatです。
闇に飲まれている筆頭として何か書こうかと思ったんですがそれは16日目にして、今日は心癒される話題(?)です。
ちなみに、エンジニアリングのえの字も出てきません。
亜熱帯オフィス
僕が毎日通っている弊社の6Fフロアは夏も冬も驚くほど暑く、室内の温度は常に25度に迫り、木枯らしが吹くような肌寒い日でも半袖で過ごせるのが特徴です。
働いている身としては眠くなるし、冬は外気温との差が大きすぎて服装の調節が難しく、体調を崩しやすくなるため出来ることなら改善してもらいたいものですが、 この特殊な環境は、とある一つの趣味を発生させます。
そう、観葉植物の育成です。
こちらは僕と同じチームの先輩のデスクの様子です(※特別な許可を得て撮影しています)
このうちいくつかは光触媒の材質で作られた空気清浄作用をもつ人工物とのことですが、 いかがでしょうか、まさにジャングルです。
一年を通して温暖な気候が保たれる上に日付が変わってもなお何時間にもわたって煌々と人工の光を湛える僕らのオフィス[要出典]は、 あたたかい地域でこそ青々とした葉を伸ばす観葉植物たちにとって、うってつけの環境なのです。
机上シルクジャスミン
そんな訳で、僕もはじめてみました。
シルクジャスミンという種で、和名をゲッキツ(月橘)といい、名前が醸し出す通りミカン科の植物です。
詳しいバイオロジーが知りたい場合はWikiでも見て頂くとして。
この美しさ。
一年中つやのある鮮やかな緑の葉を輝かせ、幹はなめらかで白けたオーガニックな雰囲気を纏うその姿はまるで生命力の象徴のようでもあり、 また神聖な儀式祭具のようですらあります。
そもそも、名前からして神々しさがあります。
今、俺の机の上にはシルクジャスミンがあるんだ
というだけで、ふだん、開発する機械[要出典]のように働いているわたしたちに、何か忘れかけていた誇りのようなものを思い出させてくれる気がしませんか。
「かんなぎ」という作品を読んだことがあるでしょうか。 3巻に、
あの時の きれいなひとが 家にいる
―― それだけで いつも うれしかった
(※若干の整形を施してあります)
という一節がありますが、そんな感じです。
苗木ケア
このシルクジャスミンですが、机の上に置く観葉植物としてはかなり変わり種のようです(そもそもミカンですし)。 というのも、いくつか普通の観葉植物にはあまり見られない特徴があります。
かなり水をあげて平気 (乾燥に弱い)
大抵の観葉植物は水やりの過多に弱く、あげる周期は土の表面が乾いてから3,4日後、というのがセオリーです。
しかしこのシルクジャスミン、土の表面が白く水分が飛んだ状態になると途端に目に見えて顔色が悪くなるため、土が乾ききったタイミングで間を空けずに水やりを行うくらいの気概が必要になります。
つやつやの葉も乾燥に弱く、特に空調の風が直接当たるようなロケーションで育成する場合は、定期的に霧吹き等で葉の表面を保湿しておく必要があります。
↓水を滴らせると美しいの図(わかりにくい)
日照が大事
基本的に日当たりの良い場所でないと花や果実を付けるのは難しいようで、室内はなかなかにハードな条件だったりします。
あと日照って書いた時の日光東照宮感は異常。
うどんこ病に弱い
なんだそれはという話ですが、様々な植物に発生するカビの一種が原因の病気で、 罹患すると白いうどんの粉のようなものが葉に付着し、草木の健康を奪います。
シルクジャスミンは葉が乾燥するとうどんこ病になりやすいようで、やはりこの対策も水分補給が鍵 となります。
カイガラムシがすぐつく
僕の2年前の入社時に元気に葉を広げていた弊社6Fの2本の観葉植物は、今年の春ごろに揃って産業廃棄物として処分される憂き目に遭いました。 その原因となったのがカイガラムシです。
あれらはどこからともなく現れて植物の枝葉に付着し、またたく間に増え、 まるで植木がファーを纏っているかのように毛で真っ白にしてしまうおっかないやつらです(もちろんその後枯死します)。
シルクジャスミンは産地によって出荷禁止になるほどカイガラムシの獲得率が高い[要出典]とのことで、 発生実績のある弊社6Fオフィス(本当どっから入ってきたんだ)では特に注意して除去していきたいところです。
うどんこ病とあわせて、とにかく白いヤツには気をつけろということですね。 /人◕‿‿◕人\
常緑ヒーリング
シルクジャスミンは特に芽吹く力が強く、うちの木も不利な室内で育て始めてまだ1ヶ月足らずですが、 既にみずみずしい若葉を順調に伸ばしつつあります。
いつでも目線をあげれば健気に育つシルクジャスミンのある生活は、想像以上に癒しの力を秘めています。
皆さまも机の上に生命の輝きを導入してみてはいかがでしょうか。
まとめ
- 暑い
- シルクジャスミンに癒されるの良い
- 写真ヘタだしiPad Miniだけだと限界ある(エフェクトでごまかしてすみません)
あすはDarkのメインイベント、(だいたい)新卒エンジニア向け技術交流会 vol.5です!