【AI Shift Advent Calendar 2023】AI Shift Voicebotのアーキテクチャについて

はじめに

AI Shiftでバックエンドを担当している須永です。
この記事はAI Shift Advent Calendar 2023の13日目の記事です。
AI Shiftでは、現在Voicebotを開発しています。
今回はAI ShiftのVoicebotのアーキテクチャについて紹介したいと思います。

クリーンアーキテクチャについて

Voicebotでは、クリーンアーキテクチャベースのアーキテクチャとなっています。

クリーンアーキテクチャは、ソフトウェア設計の一つのパラダイムで、Robert C. Martinによって提唱されました。このアーキテクチャの目的は、ソフトウェアの設計をより理解しやすく、メンテナンスしやすく、そして柔軟にすることです。

クリーンアーキテクチャでは各層がどのような責務をもつべきかを定義しています。これによりどこに何を実装すべきかということに迷いづらくなります。また、このことはクリーンアーキテクチャが重要視している依存性のルールを考えることも容易にしてくれます。

クリーンアーキテクチャでは、内側の層は外側の層に依存せず、逆に外側の層が内側の層に依存するようにしています。ここでいう内側は不変的であるアプリケーションのビジネスルールやユースケースです。逆に外側はデータベースやフレームワークなどです。そのため、仮に外側が変更されてもビジネスロジックは影響を受けないなどのメリットがあります。

クリーンアーキテクチャの文脈でよく紹介される下記の図をみることでイメージをつかみやすくなります。

全体の構成

全体の構成は下記のようになっています。
太字にした部分を今回特に注目して解説していきます。

domain層

domain層はビジネスロジックやビジネスルールをモデル化したオブジェクトやメソッドを含む層を実装する部分を指します。これはアプリケーションの中心的な部分で、アプリケーションがどのように動作するか、どのようなビジネスルールに従うかを定義します。

この階層はさらにmodel,repository,service層と分かれています。

model

modelはdomainの中心を担っており、特定のビジネスオブジェクト、ビジネスルールを表現するための概念やデータ構造を指しています。モデルは、アプリケーションの「知識」や「理解」をカプセル化しています。

具体的には、モデルは以下のような要素を含んでいます。

  1. エンティティ:ビジネスオブジェクトを表現するクラスや構造。
  2. ビジネスロジック:ビジネスルールを実装するメソッド。
  3. バリデーションルール:データの整合性を保つためのルール。

下記はコードのイメージです。

package model


// エンティティ
type Scene struct {
    ・
    ・
    ・
}


// ビジネスロジック
func (m Scene) ReplyMessage(ctx context.Context, currentFrame *Frame) string {
    ・
    ・
    ・
}


// バリデーションルール
func (m Scene) Validate() error {
      ・
      ・
      ・
}

repository

リポジトリ(repository)とは、データアクセスロジックをカプセル化するパターンの一つで、ドメインモデルとデータストアの間に位置します。

リポジトリの主な役割は、ドメインモデルの永続化(保存)と再構築(読み出し)を担当することです。リポジトリを使用することで、アプリケーションの他の部分はデータストアの具体的な実装(例:SQLクエリ)を気にせずに、ドメインモデルの永続化と再構築を行うことができます。

また、ここでは、インターフェースの定義のみを行っており、実際の実装はinfrastructure層で行っています。下記のようなイメージです。

type BusinessHourRepository interface {
  Get(
    ctx context.Context,
    query GetBusinessHourQuery,
  ) (*model.BusinessHour, error)
  List(
    ctx context.Context,
    query ListBusinessHourQuery,
  ) (model.BusinessHours, error)
  Store(
    ctx context.Context,
    m *model.BusinessHour,
  ) (*model.BusinessHour, error)
}

service

複数のdomain modelを扱うビジネスロジックを記述する層になります。
下記のようなイメージです。

func (t *twilioProjectRelationService) CanWrite(
  ctx context.Context,
  tenantID string,
  param TwilioProjectRelationCanWriteParam,
) (bool, error) {
  twilioPhoneNumber, err := t.twilioPhoneNumberRepository.Get(
    ...
  )
  productionPhoneNumbers, err := t.twilioProjectRelationRepository.List(
    ...
  )
  ...
}

infrastructure層

この層ではmysqlやredisなど外界との接続を担っています。repositoryで定義したインターフェースの具体的な実装が書いてあったりします。

http

handlerは別で分けるプロジェクトもあると思いますが、クリーンアーキテクチャの原則に従って
この層にhttpというを層を作ってhandlerを含めています。

infrastructureの中にhttp(handler)を含めた理由は、http(handler)も外界との接続を担当するという意味でmysqlやredisなどと同じような性質を持っているからです。

mysql

mysqlの層は下記のようになっています。

AI Shiftではsqlboilerを利用していますが、もし生のSQLを利用している場合はここにSQL文がくるイメージです。

func (b *businessHourRepository) Get(ctx context.Context, query repository.GetBusinessHourQuery) (*model.BusinessHour, error) {
    mods := b.buildGetQuery(query)
    dbm, err := dbmodel.DeBusinessHours(
    mods...,
  ).One(ctx, transactable.GetContextExecutor(ctx)) 
    ・
    ・
    ・
  return marshaller.BusinessHourToModel(dbm, holidays), nil
}

marshaller

JSONでレスポンスを返すにしても、mysqlにデータを保存するにしても、それに応じたDTOに変換する必要がありますが、これもhttpやmysqlディレクトリ配下でmarshallerというディレクトリを作り、model⇔dtoのコードをまとめています。

usecase層

usecaseは、アプリケーションの特定の操作や機能(ユースケース)を定義します。この層は、ユーザーがシステムとどのように対話するか、またはシステムがどのように動作するかを定義します。ここまで紹介したdomain層のmodelやrepository、serviceを利用して実装を実現しています。

その他の工夫

2つのアプリケーション

Voicebotは大きくvoice_admin(BOTの動きを事前に決める管理画面)とvoice_bot(実際に動作するBOT)という2つのアプリケーションから成り立っています。

voice_botとvoice_adminは別のアプリケーションですが、システムとして見ると一つのソフトウェアとして捉えられるので共通化しています。

同じバイナリをcobraというツールで起動方法をわけることで別々のアプリケーションとして動かすことができます。

ディレクトリの工夫

handler、usecase層配下はどのmodelを対象にするかに応じてディレクトリを切り分けています。

また、各APIでやることは多くの場合CRUD操作なので、ファイル名もcreate,list,updateのように同じ名前を利用することで見通しをよくしています。

これらの工夫によって、「何を/どうするのか」という見通しがよくなります。

テストコードについて

テストコードは基本的にはmodelの各メソッドに対して書いています。テストコードをテストコードとして別に分けるのではなく、同じディレクトリ配下におくことで、どのテストがどこにあるのかがすぐにわかるようになっています。

usecase層でもテストコードをusecaseディレクトリの中で各usecaseに対して書いています。また、アーキテクチャの話からそれますが、go mockを利用することでテストをしやすくしています。

まとめ

以上がAI ShiftのVoicebotの構成になります。

クリーンアーキテクチャそのものはよくある構成ですが、AI Shift独自の細かい工夫がVoicebot開発を円滑に進める助けになっています。

最後に

AI Shiftではエンジニアの採用に力を入れています!
少しでも興味を持っていただけましたら、カジュアル面談でお話しませんか?
(オンライン・19時以降の面談も可能です!)
【面談フォームはこちら】
https://hrmos.co/pages/cyberagent-group/jobs/1826557091831955459

明日はAIチームの干飯によるCPythonのメモリ管理についての記事になります!

PICK UP

TAG