人間のあるべき姿の探索

思索・人形・エンジニアリング

Dev ContainerとDependency Injectionを活用してロボット開発をする

コンテナ技術について学ぶために本を買いました。Azureを用いたコンテナアプリケーション開発についての書籍なのですが、最初にDev ContainerというVS Code拡張機能をもちいた開発方法について記載があり、面白かったので紹介します。

gihyo.jp

VS CodeのDev Containerとは

端的に述べると、開発環境をVS Code+Dockerで用意できるものになります。Dockerコンテナを起動し、ソースコードをマウントしてホストOSと共有することで、開発環境をコンテナに閉じ込めながらホストOSのVS Code上で開発ができます。また、ここでのコンテナはローカルマシンに限らずAzureのようなクラウドサービス上のものも指定できる為、sshのようにリモートマシンでCPUなどのリソースを気にせずに開発ができそうですね(まだそこまで読んでないですが、、、)

あとで使うので、他サイトから図を参考にさせていただきつつ作っておきます。

blog.kinto-technologies.com

Dependency InjectionによるMockの作成

DIについて、パッと記事を紹介して理解して…というのも難しいので、具体的なユースケースと共に紹介します。オブジェクト指向等の背景を前提として、プログラムを書くときに呼び出し元と呼び出し先の依存関係を疎結合にしたい欲求に答えてくれるパターンの一つです。僕は単体テストに用いるMock Repositoryの例で学習したのですが、例えば在庫管理アプリでSQLデータベースに在庫情報を永続化している場合を考えます。ここで、永続化を担うRepositoryとそれを呼び出すBusiness Logicのクラスがいたとして、Business Logic側のプログラムではrepository = new StoreRepository();みたいにインスタンス化します。…が、Repositoryは実際にデータベースに接続しますが、ダミーデータでテストしたいといった場合にオンメモリの在庫インスタンスを用いてテストを書きたい、みたいなケースが生じます。その際Business Logicのクラスのインスタンス作成部分をいちいち書き換えて…とはやっていられません。

そこで、DIコンテナというのが登場します。DIコンテナはアプリケーション実行環境におけるインターフェースと具象クラスの対応付けを記載するとアプリケーション内ではインターフェースの型を用いてコードを記述するだけで勝手に具象クラスを使うようにしてくれます。

例えばPythonではinjectorというライブラリを使用してこんな感じで書きます。

DIコンテナ側

class Dependency():
    def __init__(self) -> None:
        self.injector = Injector(self.__class__.config)
   
    # write DI source and target    
    @classmethod
    def config(cls, binder):
        binder.bind(IRepository, to=MockRepository)

アプリケーション側

class Robot:
    @inject
    def __init__(self, repository: IRobotConnector, mode: Mode = Mode.MANUAL) -> None:
        self.repository: IRepository = repository

アプリケーション側のコードでは、repositoryには実際にDBに接続するクラスが入ってくるか、テスト用のMockが入ってくるか記述されておらず、DIコンテナを書き換えることで実際に使用する具象クラスが決定されます(PythonなのですがC#の癖でインターフェースをIHogehogeと書いてますね、、、)。これによって、アプリケーション全体で使用する具象クラスを一か所にまとめられます。ちなみにPythonにはC#で言うところのInterfaceが存在せず抽象クラスで無理やりDIすることになるのでPythonの必要性がなければ素直にC#を使った方が良いです。

ちゃんと知りたい方はまともな記事を読んだり実際にコーディングすると良いです。

www.cresco.co.jp

今回使用したプログラムですが、以前DIを用いつつレイヤードアーキテクチャ的な考えの元にロボットのプログラムを書きました、今回はこのプログラムを使用します(必要に応じて紹介していきます)

godiva-frappuccino.hatenablog.com

とりあえず、ロボットのプログラムを再度紹介します。レイヤードアーキテクチャそれ自体が重要というよりは、Infrastructure層を切り替えることでMockと実際のロボットを操作するボードに出力先を切り替えて、実機なしのテストができるところにメリットがあります。

こんな感じで、UDPでメッセージをもらって動く実アプリケーションとして動かすか、テストコードを実行するか選びつつ、生成された動作をMockに流すかArduino(モータなどを動かせるマイコンボード)に流すか簡単に切り替えられます。

Dev ContainerとDIを組み合わせる

それぞれの活用方法をおさらいすると、まずDev Containerではコンテナ上にアプリケーションが走る環境を構築できます。ロボットのプログラムの場合、Ubuntuでないと動かないとか、特定のパッケージを入れないといけないとかミドルウェアの設定がどうとかあり、実機だと結構面倒ごとが多いです。僕はたまたまWindowsで開発してるので実はホストOSでも開発ができるのですが、特にPythonの環境は綺麗にしておきたいので、コンテナに閉じ込めるメリットがあります(余談ですが、ロボット開発をWindowsでやるのもPythonでやるのもありえないという風潮がありますが、僕もあり得ないと思います)。

そして、DIを用いることで実機なしのテストが可能になります。Dev Containerの設定方法についてはよりきれいにまとまったブログに任せますが、実際に実行してみます。

テストコードでは0.2秒ごとに3つのサーボモータに角度を与え、1秒ごとにMockが標準出力に現在姿勢を出します(実機だと1秒ごとにロボットの姿勢が更新されます)。

下にDev Container: Javaと書いてある通り、これがコンテナ上で走ってくれます(設定の名前JavaのままでしたがPythonで動いてます)。

ちなみに、Dockerはポートを開ければホストOSとのやりとりもできるので、Infrastructure層にシミュレータとソケット通信でやりとりするクラスを用意すれば以下のような構成でホストOS上に建てたシミュレータを動かす、みたいなこともできます。

 

かなり夢が広がりますね…