$30 off During Our Annual Pro Sale. View Details »

AI組織でのアプリケーション開発の裏側 - Django × DDDでの開発プラクティス

AITC - ISID
November 17, 2022

AI組織でのアプリケーション開発の裏側 - Django × DDDでの開発プラクティス

2022年11月16日(水)開催、エンタープライズAIとモダンアプリケーション開発の実践~ISID✕Microsoft~ #2の 「AI組織でのアプリケーション開発の裏側 - Django × DDDでの開発プラクティス​」セッションでの発表スライドです。

AITC - ISID

November 17, 2022
Tweet

More Decks by AITC - ISID

Other Decks in Technology

Transcript

  1. AI組織でのアプリケーション開発の裏側 - Django × DDDでの開発プラクティス 電通国際情報サービス(ISID) X(クロス)イノベーション本部 AIトランスフォーメーションセンター ⼭⽥ 侑樹

    (Yuki YAMADA)
  2. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. ⼭⽥ 侑樹(Yuki YAMADA) 電通国際情報サービス Xイノベーション本部

    AIトランスフォーメーションセンター 所属 ⾃⼰紹介 2
  3. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. 私たちが取り組んでいる開発スタイル、プラクティスについてを紹介 • 私たちはどんなチームか︖ • チームの特徴や抱えていた課題感の共有

    • どのようにDDDのプラクティスを実践しているか︖ • ドメイン分析プロセスの流れ • Djangoを使ったプロジェクトで、ドメイン分析の結果をコードに反映させる⽅法 今⽇お話すること 3
  4. 4 私たちはどんなチームか︖ - 開発スタイル、⼈、技術スタック

  5. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. 皆さんはどんな開発スタイルでアプリケーションを開発していますか︖ アプリケーションの開発スタイル 5 ウォーターフォール開発 アジャイル開発

  6. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. 皆さんはどんな開発スタイルでアプリケーションを開発していますか︖ アプリケーションの開発スタイル 6 ウォーターフォール開発 アジャイル開発

    私たちは3〜8⼈の規模でのアジャイル開発に取り組んでいます
  7. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. アジャイルソフトウェア開発宣⾔ 7 私たちは、ソフトウェア開発の実践あるいは実践を⼿助けをする活動を通じて、よりよい開発⽅法を⾒つけだそうとしている。 この活動を通して、私たちは以下の価値に⾄った。 プロセスやツールよりも個⼈と対話を、

    包括的なドキュメントよりも動くソフトウェアを、 契約交渉よりも顧客との協調を、 計画に従うことよりも変化への対応を、 価値とする。すなわち、左記のことがらに価値があることを認めながらも、私たちは右記のことがらにより価値をおく。 Kent Beck Mike Beedle Arie van Bennekum Alistair Cockburn Ward Cunningham Martin Fowler James Grenning Jim Highsmith Andrew Hunt Ron Jeffries Jon Kern Brian Marick Robert C. Martin Steve Mellor Ken Schwaber Jeff Sutherland Dave Thomas ˜ ্هͷஶऀͨͪ ͜ͷએݴ͸ɺ͜ͷ஫ҙॻ͖΋ؚΊͨܗͰશจΛؚΊΔ͜ͱΛ৚݅ʹࣗ༝ʹίϐʔͯ͠Α͍ɻ https://agilemanifesto.org/iso/ja/manifesto.html
  8. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. メンバーの特徴 8 少⼈数の組織では⼀⼈が複数の役割を担うことが多い 特に機械学習エンジニアがアプリケーションを開発する場⾯もある データサイエンティスト

    機械学習エンジニア
  9. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. 利⽤している技術 9 ⾔語・フレームワーク タスク管理・ドキュメント管理 開発エディタ・コード管理

    動作環境
  10. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. ソフトウェア開発の難しさ • 顧客業務・要求の理解 • データ分析と異なり、書いたコードは中⻑期的に動作する

    • 継続的に⼿を加え続けられる必要がある 抱えていた課題 10 Djangoの難しさ • ⾮常に強⼒なフレームワークだが、使いこなせていない感 • Djangoモデルが秩序なく利⽤される
  11. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. ドメイン駆動設計は2つのプラクティスが前提条件 1. 開発がイテレーティブである 2. 開発者とドメインエキスパートが密接に関わっている

    ドメイン駆動設計(DDD) 11
  12. 12 どのようにDDDのプラクティス を実践しているか︖

  13. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. ドメイン分析 パターンを適⽤し、 コードに反映 DDDの実践プロセス 13

  14. ドメイン分析プロセス

  15. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. ソフトウェア開発の難しさ • 顧客業務・要求の理解 • データ分析と異なり、書いたコードは中⻑期的に動作する

    • 継続的に⼿を加え続けられる必要がある ドメイン分析プロセスで⽴ち向かう課題 15 Djangoの難しさ • ⾮常に強⼒なフレームワークだが、使いこなせていない感 • Djangoモデルが秩序なく利⽤される
  16. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. アプリケーション開発対象の分野や領域を“ドメイン”と呼ぶ プロジェクトメンバーはドメインについて確実に理解する必要があり、 ドメイン分析を通して、ドメインが固有に持つ性質や知識について明らかにする そもそも“ドメイン”とはなにか︖ 16

  17. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. 1. ⾼レベルな要求から、アプリケーションの機能要件を理解する 2. 名詞概念を中⼼にドメインモデルを抽出する 3.

    コンテキストを識別し、着⼿する部分を明らかにする ドメイン分析のプロセス 17
  18. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. アプリケーション開発を始める際に初期から⾮常に具体的な要求があるわけでは無い ⾼レベルな要求から整理しよう 18 AIを使って、アンケートの分析を楽にしたい ü

    アンケートの回答データを登録して、集計・分析したい ü 回答データから不適切なデータは取り除きたい ü 似たような回答をしている回答者をまとめたい ü センシティブなアンケートも扱うので、権限管理をして欲しい ü etc … 実務担当者
  19. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. 名詞概念を中⼼に図に起こす ドメインモデリングを実践する 19 ü 完璧を求めない

    ü オブジェクトを書いてみる ü 関係を表現する
  20. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. 対話を通して、ドメインモデルを洗練させる ドメインモデリングを実践する 20

  21. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. コンテキストを意識し、境界を識別する(境界付けられたコンテキスト) ドメインモデリングを実践する 21 ü アプリケーション全体で同じ表現を使う

    必要はない ü 回答の種類によって”分析”という⾔葉の 意味合いが異なる FA分析コンテキスト
  22. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. ドメインモデルの和名と英名を整理する ※ 意味はその時点での理解が整理できればよい ドメインモデリングを実践する 22

    和名 英名 意味 アンケート Survey アンケート アンケート設定 Survey Setting アンケートの開始⽇や終了⽇などの設定 質問 Question アンケートに含まれる質問 回答結果 Survey Result アンケート終了後の回答結果全体 回答 Answer 回答結果全体に含まれる各回答 単⼀回答(SA) Single Answer 回答の中で単⼀選択の質問に対する回答 複数回答(MA) Multi Answer 回答の中で複数選択の質問に対する回答 ⾃由回答(FA) Free Answer 回答の中で⾃由記述の質問に対する回答 分析 Analysis 分析者が実施する分析業務の単位 分析結果 Analysis Result 分析結果
  23. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. ソフトウェア開発の難しさ • 顧客業務・要求の理解 • データ分析と異なり、書いたコードは中⻑期的に動作する

    • 継続的に⼿を加え続けられる必要がある ドメイン分析プロセスで⽴ち向かう課題 23 Djangoの難しさ • ⾮常に強⼒なフレームワークだが、使いこなせていない感 • Djangoモデルが秩序なく利⽤される
  24. ドメイン分析の結果をコードに反映させる

  25. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. ソフトウェア開発の難しさ • 顧客業務・要求の理解 • データ分析と異なり、書いたコードは中⻑期的に動作する

    • 継続的に⼿を加え続けられる必要がある ドメイン分析の結果をコードに反映させるで⽴ち向かう課題 25 Djangoの難しさ • ⾮常に強⼒なフレームワークだが、使いこなせていない感 • Djangoモデルが秩序なく利⽤される
  26. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. レイヤーを分ける理由はドメインの隔離 アプリケーションのレイヤー設計 26 アプリケーション層 ドメイン層

    インフラストラクチャ層
  27. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. レイヤーを分ける理由はドメインの隔離 アプリケーションのレイヤー設計 27 アプリケーション層 ドメイン層

    インフラストラクチャ層 呼び出す 実装する
  28. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. ドメイン分析によって得られたドメインモデルをドメイン層のコードに表現して アプリケーションを組み⽴てる 実装して得られた気づきはドメインモデルにフィードバックをする ドメインモデル中⼼の考え⽅ 28

    ドメイン モデル 実装
  29. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. DDDの実装パターン 29 リポジトリ (Repository) 値オブジェクト

    (Value Object) 集約 (Aggregate) エンティティ (Entity) Application Service Domain Service サービス (Service)
  30. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. DDDの実装パターン 30 リポジトリ (Repository) 値オブジェクト

    (Value Object) 集約 (Aggregate) エンティティ (Entity) Application Service Domain Service サービス (Service)
  31. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. エンティティ・値オブジェクト・集約 31 値オブジェクト (Value Object)

    集約 (Aggregate) エンティティ (Entity) Application Service Domain Service サービス (Service) リポジトリ (Repository)
  32. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. エンティティ・値オブジェクト・集約 32 リポジトリ (Repository) “エンティティ”

    時間経過に関係なく保持される ⼀意のIDを持つオブジェクト “値オブジェクト” IDを持たず属性の値でのみ定義 されるオブジェクト 値オブジェクトは不変である “集約” 1つ以上のエンティティを含み、 ⼀貫性の境界を提供する概念 値オブジェクト (Value Object) 集約 (Aggregate) エンティティ (Entity) Application Service Domain Service サービス (Service)
  33. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. リポジトリ 33 リポジトリ (Repository) 値オブジェクト

    (Value Object) 集約 (Aggregate) エンティティ (Entity) Application Service Domain Service サービス (Service)
  34. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. リポジトリ 34 リポジトリ (Repository) 値オブジェクト

    (Value Object) 集約 (Aggregate) エンティティ (Entity) Application Service Domain Service サービス (Service) “リポジトリ” インフラストラクチャを隠蔽し、ドメイン オブジェクトの永続化と再構築する リポジトリの作成単位は集約とする
  35. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. アプリケーションのレイヤー設計との対応 35 アプリケーション層 ドメイン層 インフラストラクチャ層

    値オブジェクト (Value Object) 集約 (Aggregate) エンティティ (Entity)
  36. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. アプリケーションのレイヤー設計との対応 36 アプリケーション層 ドメイン層 インフラストラクチャ層

    リポジトリ (Repository)
  37. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. さきほどのFA分析のシナリオで考える ü分析者は分析対象名を⼊⼒して、分析を開始する ü分析者はアンケート回答結果から⾃由回答を分析に登録する üシステムは登録された各⾃由回答の⽂章を単語に分割するといった処理を実施し、 分析者に分析結果を提供する

    ドメインモデルの実装パターン 37 FA分析コンテキスト
  38. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. ドメインモデルの実装パターン 38 リポジトリ (Repository) “エンティティ”

    時間経過に関係なく保持される ⼀意のIDを持つオブジェクト “値オブジェクト” IDを持たず属性の値でのみ定義 されるオブジェクト 値オブジェクトは不変である “集約” 1つ以上のエンティティを含み、 ⼀貫性の境界を提供する概念 値オブジェクト (Value Object) 集約 (Aggregate) エンティティ (Entity)
  39. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. ドメインモデルの実装指針 39 pydanticを利⽤します

  40. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. pydanticを利⽤したAnalysisクラスの実装 Pythonでドメインモデルを表現するには 40 from typing

    import List from pydantic import BaseModel class Analysis(BaseModel): """分析クラス""" id: int name: str answers: List[FreeAnswer]
  41. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. pydanticを利⽤したFreeAnswerクラスの実装 Pythonでドメインモデルを表現するには 41 from typing

    import List from pydantic import BaseModel class FreeAnswer(BaseModel): """⾃由回答クラス""" id: int value: str tokens: List[Token]
  42. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. pydanticを利⽤したTokenクラスの実装 Pythonでドメインモデルを表現するには 42 from pydantic

    import BaseModel class Token(BaseModel): """単語クラス""" value: str class Config: frozen = True
  43. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. 1. 型ヒント( Type Hint )の恩恵を受けやすい

    2. ⼊⼒値検証(Validation)がしやすい 3. pydanticが提供する豊富な型(EmaiStrやHttpUrlなど)を利⽤できる 4. dict()メソッドによる辞書型への変換が容易 ドメインモデルに pydantic を使うメリット 43 Python 標準だけで実装するならば @dataclass を活⽤しましょう
  44. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. Analysisクラスがフィールドに持つクラスもすべて永続化する 集約に対して Repository を実装する 44

    リポジトリ (Repository) “リポジトリ” インフラストラクチャを隠蔽し、ドメイン オブジェクトの永続化と再構築する リポジトリの作成単位は集約とする
  45. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. Repositoryではインタフェースライクに抽象基底クラスを利⽤する Repository の実装指針 45 アプリケーション層

    ドメイン層 インフラストラクチャ層 リポジトリ (Repository)
  46. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. Repositoryではインタフェースライクに抽象基底クラスを利⽤する Analysis Repository の抽象基底クラス 46

    from abc import ABC, abstractmethod from fa_analysis.domains.analysis import Analysis class AbstractAnalysisRepository(ABC): @abstractmethod def save(self, analysis: Analysis) -> Analysis: """Domain層のAnalysisを永続化するメソッド Args: analysis (Analysis): Domain層のAnalysisオブジェクト Returns: Analysis: 保存済みのAnalysisオブジェクト。別のインスタンスとして返す """ ...
  47. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. Repositoryではインタフェースライクに抽象基底クラスを利⽤する Analysis Repository の抽象基底クラス 47

    from abc import ABC, abstractmethod from fa_analysis.domains.analysis import Analysis class AbstractAnalysisRepository(ABC): @abstractmethod def save(self, analysis: Analysis) -> Analysis: """Domain層のAnalysisを永続化するメソッド Args: analysis (Analysis): Domain層のAnalysisオブジェクト Returns: Analysis: 保存済みのAnalysisオブジェクト。別のインスタンスとして返す """ ...
  48. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. Repositoryではインタフェースライクに抽象基底クラスを利⽤する Analysis Repository の抽象基底クラス 48

    from abc import ABC, abstractmethod from fa_analysis.domains.analysis import Analysis class AbstractAnalysisRepository(ABC): @abstractmethod def save(self, analysis: Analysis) -> Analysis: """Domain層のAnalysisを永続化するメソッド Args: analysis (Analysis): Domain層のAnalysisオブジェクト Returns: Analysis: 保存済みのAnalysisオブジェクト。別のインスタンスとして返す """ ...
  49. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. Repositoryではインタフェースライクに抽象基底クラスを利⽤する Analysis Repository の抽象基底クラス 49

    from abc import ABC, abstractmethod from fa_analysis.domains.analysis import Analysis class AbstractAnalysisRepository(ABC): @abstractmethod def save(self, analysis: Analysis) -> Analysis: """Domain層のAnalysisを永続化するメソッド Args: analysis (Analysis): Domain層のAnalysisオブジェクト Returns: Analysis: 保存済みのAnalysisオブジェクト。別のインスタンスとして返す """ ... Repositoryのインターフェース設計では引数と返り値の 型をドメインオブジェクトとする
  50. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. Repository のインタフェースはドメイン層のクラスで表現される Repository の実装指針 50

    アプリケーション層 ドメイン層 インフラストラクチャ層 リポジトリ (Repository)
  51. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. 実装クラスでは抽象クラスを継承し内部では Django モデルを利⽤して RDB にドメインモデルを永続化する

    抽象クラスを継承した Repository の実装指針 51
  52. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. Repository の実装コード 52 from fa_analysis.domains

    import Analysis as DomainAnalysis from fa_analysis.models.analysis import Analysis, FreeAnswer, Token class AnalysisRepository(AbstractAnalysisRepository): """AnalysisRepositoryの実装""" def save(self, analysis: DomainAnalysis) -> DomainAnalysis: db_analysis = Analysis.from_domain(obj=analysis) db_answers = [] db_tokens = [] for answer in analysis.answers: db_answers.append(FreeAnswer.from_domain(obj=answer, analysis_id=analysis.id)) for token in answer.tokens: db_tokens.append(Token.from_domain(obj=token, answer_id=answer.id)) db_analysis.save() FAAnswer.objects.bulk_create(db_answers) Token.objects.bulk_create(db_tokens) return db_analysis.to_domain()
  53. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. Repository の実装コード – 抽象クラスの継承 53

    from fa_analysis.domains import Analysis as DomainAnalysis from fa_analysis.models.analysis import Analysis, FreeAnswer, Token class AnalysisRepository(AbstractAnalysisRepository): """AnalysisRepositoryの実装""" def save(self, analysis: DomainAnalysis) -> DomainAnalysis: db_analysis = Analysis.from_domain(obj=analysis) db_answers = [] db_tokens = [] for answer in analysis.answers: db_answers.append(FreeAnswer.from_domain(obj=answer, analysis_id=analysis.id)) for token in answer.tokens: db_tokens.append(Token.from_domain(obj=token, answer_id=answer.id)) db_analysis.save() FAAnswer.objects.bulk_create(db_answers) Token.objects.bulk_create(db_tokens) return db_analysis.to_domain() as による別名インポート 抽象クラスの継承 実装メソッド
  54. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. Repository の実装コード – Djangoモデル 54

    from fa_analysis.domains import Analysis as DomainAnalysis from fa_analysis.models.analysis import Analysis, FreeAnswer, Token class AnalysisRepository(AbstractAnalysisRepository): """AnalysisRepositoryの実装""" def save(self, analysis: DomainAnalysis) -> DomainAnalysis: db_analysis = Analysis.from_domain(obj=analysis) db_answers = [] db_tokens = [] for answer in analysis.answers: db_answers.append(FreeAnswer.from_domain(obj=answer, analysis_id=analysis.id)) for token in answer.tokens: db_tokens.append(Token.from_domain(obj=token, answer_id=answer.id)) db_analysis.save() FAAnswer.objects.bulk_create(db_answers) Token.objects.bulk_create(db_tokens) return db_analysis.to_domain() Djangoモデル ファクトリメソッドで Djangoモデルにマッピング
  55. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. Repository の実装コード – Djangoモデル 55

    from fa_analysis.domains import Analysis as DomainAnalysis from fa_analysis.models.analysis import Analysis, FreeAnswer, Token class AnalysisRepository(AbstractAnalysisRepository): """AnalysisRepositoryの実装""" def save(self, analysis: DomainAnalysis) -> DomainAnalysis: db_analysis = Analysis.from_domain(obj=analysis) db_answers = [] db_tokens = [] for answer in analysis.answers: db_answers.append(FreeAnswer.from_domain(obj=answer, analysis_id=analysis.id)) for token in answer.tokens: db_tokens.append(Token.from_domain(obj=token, answer_id=answer.id)) db_analysis.save() FAAnswer.objects.bulk_create(db_answers) Token.objects.bulk_create(db_tokens) return db_analysis.to_domain() Djangoモデルの save()やbulk_create() を利⽤してRDBへ永続化
  56. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. Repository の実装コード – 返り値 56

    from fa_analysis.domains import Analysis as DomainAnalysis from fa_analysis.models.analysis import Analysis, FreeAnswer, Token class AnalysisRepository(AbstractAnalysisRepository): """AnalysisRepositoryの実装""" def save(self, analysis: DomainAnalysis) -> DomainAnalysis: db_analysis = Analysis.from_domain(obj=analysis) db_answers = [] db_tokens = [] for answer in analysis.answers: db_answers.append(FreeAnswer.from_domain(obj=answer, analysis_id=analysis.id)) for token in answer.tokens: db_tokens.append(Token.from_domain(obj=token, answer_id=answer.id)) db_analysis.save() FAAnswer.objects.bulk_create(db_answers) Token.objects.bulk_create(db_tokens) return db_analysis.to_domain() Djangoモデルをドメインモデルに変換して返す
  57. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. Repository の実装コード 57 from fa_analysis.domains

    import Analysis as DomainAnalysis from fa_analysis.models.analysis import Analysis, FreeAnswer, Token class AnalysisRepository(AbstractAnalysisRepository): """AnalysisRepositoryの実装""" def save(self, analysis: DomainAnalysis) -> DomainAnalysis: db_analysis = Analysis.from_domain(obj=analysis) db_answers = [] db_tokens = [] for answer in analysis.answers: db_answers.append(FreeAnswer.from_domain(obj=answer, analysis_id=analysis.id)) for token in answer.tokens: db_tokens.append(Token.from_domain(obj=token, answer_id=answer.id)) db_analysis.save() FAAnswer.objects.bulk_create(db_answers) Token.objects.bulk_create(db_tokens) return db_analysis.to_domain()
  58. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. Django モデルの実装コード - Analysis 58

    from django.db import models from fa_analysis.domains.analysis import Analysis as DomainAnalysis class Analysis(models.Model): """Domain層のAnalysisとマッピングされるDjangoモデル""" id = models.IntegerField(primary_key=True) name = models.CharField(max_length=256) @classmethod def from_domain(cls, obj: DomainAnalysis) -> "Analysis": """ドメインモデルからのファクトリメソッド""" return cls(id=obj.id, name=obj.name) def to_domain(self) -> DomainAnalysis: """Djangoモデルからドメインモデルに変換するメソッド""" return DomainAnalysis( id=self.id, name=self.name, answers=[a.to_domain() for a in FreeAnswer.objects.filter(analysis=self)], )
  59. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. Django モデルの実装コード - Analysis 59

    from django.db import models from fa_analysis.domains.analysis import Analysis as DomainAnalysis class Analysis(models.Model): """Domain層のAnalysisとマッピングされるDjangoモデル""" id = models.IntegerField(primary_key=True) name = models.CharField(max_length=256) @classmethod def from_domain(cls, obj: DomainAnalysis) -> "Analysis": """ドメインモデルからのファクトリメソッド""" return cls(id=obj.id, name=obj.name) def to_domain(self) -> DomainAnalysis: """Djangoモデルからドメインモデルに変換するメソッド""" return DomainAnalysis( id=self.id, name=self.name, answers=[a.to_domain() for a in FreeAnswer.objects.filter(analysis=self)], ) ドメインモデルからのファクトリメソッドの実装
  60. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. Django モデルの実装コード - Analysis 60

    from django.db import models from fa_analysis.domains.analysis import Analysis as DomainAnalysis class Analysis(models.Model): """Domain層のAnalysisとマッピングされるDjangoモデル""" id = models.IntegerField(primary_key=True) name = models.CharField(max_length=256) @classmethod def from_domain(cls, obj: DomainAnalysis) -> "Analysis": """ドメインモデルからのファクトリメソッド""" return cls(id=obj.id, name=obj.name) def to_domain(self) -> DomainAnalysis: """Djangoモデルからドメインモデルに変換するメソッド""" return DomainAnalysis( id=self.id, name=self.name, answers=[a.to_domain() for a in FreeAnswer.objects.filter(analysis=self)], ) Djangoモデルからドメインモデルへの変換メソッド to_domain() で統⼀する
  61. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. Django モデルの実装コード – FreeAnswer 61

    class FreeAnswer(models.Model): """Domain層のFAAnswerとマッピングされるDjangoモデル""" # フィールドは省略 @classmethod def from_domain(cls, obj: DomainFreeAnswer, analysis_id: int) -> "FreeAnswer": """ドメインモデルからのファクトリメソッド""" return cls( id=obj.id, value=obj.value, analysis=Analysis(id=analysis_id), ) def to_domain(self) -> DomainFreeAnswer: return DomainFreeAnswer( id=self.id, value=self.value, tokens=[t.to_domain() for t in Token.objects.filter(free_answer=self)], )
  62. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. Django モデルの実装コード – FreeAnswer 62

    class FreeAnswer(models.Model): """Domain層のFAAnswerとマッピングされるDjangoモデル""" # フィールドは省略 @classmethod def from_domain(cls, obj: DomainFreeAnswer, analysis_id: int) -> "FreeAnswer": """ドメインモデルからのファクトリメソッド""" return cls( id=obj.id, value=obj.value, analysis=Analysis(id=analysis_id), ) def to_domain(self) -> DomainFreeAnswer: return DomainFreeAnswer( id=self.id, value=self.value, tokens=[t.to_domain() for t in Token.objects.filter(free_answer=self)], )
  63. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. Django モデルの実装コード - Token 63

    class Token(models.Model): """Domain層のTokenとマッピングされるDjangoモデル""" # フィールドは省略 @classmethod def from_domain(cls, obj: DomainToken, answer_id: int) -> "Token": """ドメインモデルからのファクトリメソッド""" return cls(value=obj.value, free_answer=FreeAnswer(id=answer_id)) def to_domain(self) -> DomainToken: return DomainToken(value=self.value)
  64. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. Django モデルの実装コード - Token 64

    class Token(models.Model): """Domain層のTokenとマッピングされるDjangoモデル""" # フィールドは省略 @classmethod def from_domain(cls, obj: DomainToken, answer_id: int) -> "Token": """ドメインモデルからのファクトリメソッド""" return cls(value=obj.value, free_answer=FreeAnswer(id=answer_id)) def to_domain(self) -> DomainToken: return DomainToken(value=self.value)
  65. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. Django モデルの実装コード - Token 65

    class Token(models.Model): """Domain層のTokenとマッピングされるDjangoモデル""" # フィールドは省略 @classmethod def from_domain(cls, obj: DomainToken, answer_id: int) -> "Token": """ドメインモデルからのファクトリメソッド""" return cls(value=obj.value, free_answer=FreeAnswer(id=answer_id)) def to_domain(self) -> DomainToken: return DomainToken(value=self.value) Djangoの難しさ • ⾮常に強⼒なフレームワークだが、使いこなせていない感 • Djangoモデルが秩序なく利⽤される
  66. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. Repositoryのテストコード 66 class TestAnalysisRepository: @pytest.fixture

    def init_test(self) -> None: self.repository = AnalysisRepository() @pytest.mark.django_db def test_save(self, init_test: str) -> None: tokens = [ Token(value="すもも"), Token(value="も"), Token(value="もも"), Token(value="も"), Token(value="もも"), Token(value="の"), Token(value="うち"), ] answers = [FreeAnswer(id=1, value="すもももももももものうち", tokens=tokens)] analysis = Analysis(id=1, name="分析サンプル", answers=answers) result = self.repository.save(analysis=analysis) assert result == analysis
  67. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. https://github.com/wf-yamaday/dllab-ddd-sample 本⽇のコードのリポジトリ 67

  68. 68 まとめ

  69. COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. ソフトウェア開発の難しさ 今⽇お話したこと 69 Djangoの難しさ ドメイン分析

    パターンを適⽤し、 コードに反映 DDD(ドメイン駆動設計) ü 顧客業務・要求の理解促進 ü 実装におけるDjangoの役割を明確化し、よりクリーンなコードへ
  70. 採⽤案内

  71. ISID AITCでは新しい仲間を募集しています 少しでも興味がある⽅は、是⾮、まずはカジュアルにお話しましょう! 是⾮、気軽にカジュアル⾯談フォームからご応募ください! カジュアル⾯談フォーム https://forms.office.co m/r/a6NuyRU3t3

  72. ISID AITCでは新しい仲間を募集しています また、右側の職種への応募⽤ページからご応募頂いても⼤丈夫です。 コンサルティング系 AIコンサルタント https://groupcareers.isid. co.jp/pgisid/u/job.phtml? job_code=591&company _code=1 AIビジネスプロジェクトマネージャー

    https://groupcareers.isid. co.jp/pgisid/u/sp/job.pht ml?job_code=532 製品開発系 AIエンジニア(製品開発) https://groupcareers.isid. co.jp/pgisid/u/job.phtml? job_code=647&company _code=1 AIプロダクトマネージャー https://groupcareers.isid. co.jp/pgisid/u/job.phtml? job_code=693&company _code=1 職種ごと応募ページ
  73. 新卒採⽤もやってます ISIDでは、「データサイエンス職」という新卒応募枠をご⽤意しています。データサイエンス枠で合格された ⽅は、AIトランスフォーメーションセンターへの配属が確約されます。興味がある⽅、是⾮ご応募ください。 問い合わせ先: 株式会社 電通国際情報サービス ⼈事部 新卒採⽤・インターンシップ担当 public-fresh@isid.co.jp