Tomofiles Note

ドローンとインターネット、そして人との関係を考えるソフトウェアエンジニアのアウトプットブログ

Playful-Core -インターネットラジオ録音・クラウドアップロード-

2019/04/05 追記
現在は、ここで紹介しているGithubリポジトリの場所が変わっています。

playful-core/dustbox/playful-reservation at master · Tomofiles/playful-core · GitHub



今回は最近ちょっとずつ作ってるサービスの紹介をしようと思います。
内容があれなんで一般公開は出来ないですが、自分の困ってることを解決するためなので、良いかなと思います。

なにこれ

インターネットラジオ Radikoを録音するサービスです。
深夜ラジオ聴きたいけど、夜中なので録音してあとで聴きたい、と思ってる人は多いと思います。
そんな人のために、ラジオの番組表から聴きたい番組を指定して、録音予約をしてあげるサービスです。
最終的には、Google Play Musicにアップロードまでやってくれるようになります。

ただ現実は

Radikoの録音サービスは、現在どこにも存在しません。
もちろん、Youtube等への二次配布と同じで、著作権侵害に当たるからだと思われます。
個人の環境に仕込んで、あくまでも私的利用の範囲であれば問題ないはずなので、
cronで仕込めるようなシェルスクリプトや、Windowsデスクトップアプリなどは公開されています。
私の環境だと、インターネットがたまに死ぬので、結構な頻度で録音できていないことがあるので、
そういう個人の環境による問題がサービス化したらなくなるのではと思いますが、
まぁ現実的には公開とまではいきません。

機能概要

Playful-Coreは次のような機能を有してます(予定ですが)
・日時、録音時間による、ラジオ録音の予約情報の管理
・録音機能
・音声データの管理
Google Play Musicへのアップロード

Coreは本当にコア機能しかなく、Playfulシリーズとして、番組表機能と、予約管理機能が、別で存在します。
現時点では、そちらは全く考えていないですが、境界付けられたコンテキストに則って、区切ってます。

ソースコード

Githubに公開しています。

github.com

公開してるし、こんな紹介ブログを書いていますが、まだ完成していません。
目的は、技術やスキルのアウトプットなので、そのきっかけになればということで公開しました。

技術的な話

全体構成は以下のような感じです。

f:id:Tomofiles:20190312232405j:plain
Playful-Core 全体構成

Playful-Coreは、Reservation(録音予約)という集約を中心としたモデルで構成されます。
Reservationがコアドメイン、それ以外のRecording(録音)、PlayMusic(クラウドアップロード)がサブドメインのイメージです。
ドメイン間はイベントによる非同期的な連携を行い、機能を実現しています。
イベントソーシングとまではいきませんでしたが、イベントドリブンなアーキテクチャは目指しています。
言語はGo言語、イベントPubSubはMQTTを用いています。
コンテナ管理はKubernetes、CI/CDでビルドとリリースを回しています。(この辺は、完全に適当な構成になっています)

現在ある程度形になってるのが、録音予約管理を司るReservationのみです。
ReservationはRestful APIで録音予約情報のリソース管理ができます。
認証は機能をシンプルにするために排除してますが、本来だったらちゃんと考えないとですね。
API管理には、Swaggerを使っています。Swaggerから吐き出したgoサーバモジュールのモデル部分のみ、使用するようにしています。
本当はMQTT部分のIFもそういうの使いたかったのですが、唯一見つけたAsyncAPIがGo非対応のようなので諦めました。
データベースは完全にDocker上に立ててるので、揮発性になっちゃってます。
この辺はKubernetesの勉強をもっとやらないと、ストレージ周りの知識が足りてなくて実装できてません。

ヘキサゴナルアーキテクチャ

私のシステム設計のルーツは、実践ドメイン駆動設計という書籍なので、この思想に大いに影響されてます。

この書籍では、純粋なビジネスモデルを中心とするアーキテクチャを、ヘキサゴナルアーキテクチャと紹介しています。
巷では、クリーンアーキテクチャやオニオンアーキテクチャなどもよく耳にしますが、問題領域は同じことを言っていますね。
私はこのアーキテクチャを、いい感じにシンプルに解釈して適用しています。

Reservationプロジェクトを例に説明していきます。
構成は大別して、以下の通りです。

  • playful-core/playful-reservation/src/tomofiles/api
  • playful-core/playful-reservation/src/tomofiles/application
  • playful-core/playful-reservation/src/tomofiles/domain
  • playful-core/playful-reservation/src/tomofiles/infrastructure

レイヤー別に、説明していきます。

ドメインレイヤー

domainがすべての中心です。ここには、何らかのフレームワーク、基盤、外部システムへの依存はありません。
逆に依存される立場としています。
ここに、すべてのビジネスロジックが集中しています。例えば、データの重複条件、制約、そして、何かが達成したときのイベント、などです。

Reservationは集約エンティティです。
集約エンティティがオブジェクトを束ねることで、

  1. エンティティをリポジトリから見つける
  2. エンティティを操作する
  3. エンティティをリポジトリに保存する

という手順でビジネスが抽象化され、モデルを使った機能実現を簡易にしています。

Reservationでは、書き込みとキャンセルの操作を用意してます。
予約業務って、予約簿に書き込むか、取り消すかだと思うので、モデルにもその考え方をそのまま反映してます。
書き込みとキャンセルは、ある条件を満たすとイベントが発生します。
例えば、指定した時刻はすでに過ぎていること、指定した時刻がもうすぐ到来すること、直近の予約が取り消されたこと、などです。
このイベント、ドメインイベントが強力な戦術で、ある業務にまつわる様々な状況を別の業務に通知することで、全体で見たときの機能実現につなげます。
上のイベントの例だと、Reservationから発行された、指定した時刻が過ぎているイベントは、Recordingが関心があり、購読しています。
Recordingがイベントを受信すると、ラジオの録音を始めます。Recordingは、他の業務領域内のルールが決めたタイミングで動作するため、業務がシンプルになります。

こうしたイベントの連鎖で機能を実現することで、一枚岩のシステムより柔軟な構成をとることができるのです。

アプリケーションレイヤー

applicationは、domainを用いて業務要件を達成するレイヤーです。
Reservationでは、録音予約情報の登録、更新、削除、参照の機能を提供しています。
その機能を実現するにあたり、ドメインレイヤーのオブジェクトを操作して状態を変化させたり、状態を読み取ったりします。
ドメインへのアクセスの仕方は、ドメインレイヤーの項目に書いたとおり、リポジトリから集約エンティティを取得し、操作します。

アプリケーションレイヤーはシンプルなフロー制御になります。
業務の複雑さはドメインレイヤーに隠蔽されるので、ユーザの価値となる機能を実現するための手続きを、高い抽象度で記述出来ます。
集約エンティティの操作と、そこから発行されるイベントを処理に専念でき、どのような機能を実現するのかを、その構成要素の詳細から切り離すことが出来ます。
トランザクションもこのレイヤーの責務となります。

インフラストラクチャレイヤー

infrastructureは、ドメインレイヤーの永続化の実現や、その他基盤的な機能の実装を担います。
ドメインレイヤーにはエンティティの永続化をモデル化したリポジトリがありますが、リポジトリ自体はインフラストラクチャレイヤーの実装により、取得や保存を実現します。

今回作成しているReservationでは、リポジトリの実装がまるごとインフラストラクチャレイヤーに存在しますが、
この構成は正直微妙です。
エンティティの構築に関する詳細はドメインレイヤーの責務ですが、インフラストラクチャレイヤーに漏れ出てしまっています。
(このせいで、ファクトリーの再構築メソッドがパブリックスコープになってしまっています)
いずれ直せるかなぁ。

UIレイヤー

apiは、UIレイヤーです。
Reservationでは、SwaggerのモデルオブジェクトでリクエストボディのJSONを解析したり、
アプリケーションレイヤーのサービスに渡すDTOオブジェクトに変換したり、
パラメータのバリデーションチェックを行ったりするコントローラが、このレイヤーに配置されます。
コントローラは、ルーティングライブラリに差し込まれ、HTTPリクエストのルーティングに設定されます。
このレイヤーは外部ライブラリやUIに依存し、ユーザへのサービス提供形式によって実装が変わります。


以上のレイヤーの構成をイメージにすると、以下のようになります。

f:id:Tomofiles:20190312232542j:plain
Playful-Core レイヤーイメージ

今後の展望

まずは、いったん全部作りきることが目標です。
早く安心してラジオ聴きたいですし。
Recordingを作るにあたって、Goの並行プログラミングが必須になりそうなので、いま書籍買って勉強してます。
メモリ共有型じゃない並行プログラミングが初めてで、今まで「ロックしときゃいいんじゃね?」でなんとかなってたのが把握できました。並行プログラミング、難しい。

定期的にできてるとこまでの紹介とか、技術的な整理のための記事が書けると良いですね。