Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. ⼭⽥ 侑樹(Yuki YAMADA) 電通国際情報サービス Xイノベーション本部 AIトランスフォーメーションセンター 所属 ⾃⼰紹介 2

Slide 3

Slide 3 text

COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. 私たちが取り組んでいる開発スタイル、プラクティスについてを紹介 • 私たちはどんなチームか︖ • チームの特徴や抱えていた課題感の共有 • どのようにDDDのプラクティスを実践しているか︖ • ドメイン分析プロセスの流れ • Djangoを使ったプロジェクトで、ドメイン分析の結果をコードに反映させる⽅法 今⽇お話すること 3

Slide 4

Slide 4 text

4 私たちはどんなチームか︖ - 開発スタイル、⼈、技術スタック

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. 皆さんはどんな開発スタイルでアプリケーションを開発していますか︖ アプリケーションの開発スタイル 6 ウォーターフォール開発 アジャイル開発 私たちは3〜8⼈の規模でのアジャイル開発に取り組んでいます

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. メンバーの特徴 8 少⼈数の組織では⼀⼈が複数の役割を担うことが多い 特に機械学習エンジニアがアプリケーションを開発する場⾯もある データサイエンティスト 機械学習エンジニア

Slide 9

Slide 9 text

COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. 利⽤している技術 9 ⾔語・フレームワーク タスク管理・ドキュメント管理 開発エディタ・コード管理 動作環境

Slide 10

Slide 10 text

COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. ソフトウェア開発の難しさ • 顧客業務・要求の理解 • データ分析と異なり、書いたコードは中⻑期的に動作する • 継続的に⼿を加え続けられる必要がある 抱えていた課題 10 Djangoの難しさ • ⾮常に強⼒なフレームワークだが、使いこなせていない感 • Djangoモデルが秩序なく利⽤される

Slide 11

Slide 11 text

COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. ドメイン駆動設計は2つのプラクティスが前提条件 1. 開発がイテレーティブである 2. 開発者とドメインエキスパートが密接に関わっている ドメイン駆動設計(DDD) 11

Slide 12

Slide 12 text

12 どのようにDDDのプラクティス を実践しているか︖

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

ドメイン分析プロセス

Slide 15

Slide 15 text

COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. ソフトウェア開発の難しさ • 顧客業務・要求の理解 • データ分析と異なり、書いたコードは中⻑期的に動作する • 継続的に⼿を加え続けられる必要がある ドメイン分析プロセスで⽴ち向かう課題 15 Djangoの難しさ • ⾮常に強⼒なフレームワークだが、使いこなせていない感 • Djangoモデルが秩序なく利⽤される

Slide 16

Slide 16 text

COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. アプリケーション開発対象の分野や領域を“ドメイン”と呼ぶ プロジェクトメンバーはドメインについて確実に理解する必要があり、 ドメイン分析を通して、ドメインが固有に持つ性質や知識について明らかにする そもそも“ドメイン”とはなにか︖ 16

Slide 17

Slide 17 text

COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. 1. ⾼レベルな要求から、アプリケーションの機能要件を理解する 2. 名詞概念を中⼼にドメインモデルを抽出する 3. コンテキストを識別し、着⼿する部分を明らかにする ドメイン分析のプロセス 17

Slide 18

Slide 18 text

COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. アプリケーション開発を始める際に初期から⾮常に具体的な要求があるわけでは無い ⾼レベルな要求から整理しよう 18 AIを使って、アンケートの分析を楽にしたい ü アンケートの回答データを登録して、集計・分析したい ü 回答データから不適切なデータは取り除きたい ü 似たような回答をしている回答者をまとめたい ü センシティブなアンケートも扱うので、権限管理をして欲しい ü etc … 実務担当者

Slide 19

Slide 19 text

COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. 名詞概念を中⼼に図に起こす ドメインモデリングを実践する 19 ü 完璧を求めない ü オブジェクトを書いてみる ü 関係を表現する

Slide 20

Slide 20 text

COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. 対話を通して、ドメインモデルを洗練させる ドメインモデリングを実践する 20

Slide 21

Slide 21 text

COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. コンテキストを意識し、境界を識別する(境界付けられたコンテキスト) ドメインモデリングを実践する 21 ü アプリケーション全体で同じ表現を使う 必要はない ü 回答の種類によって”分析”という⾔葉の 意味合いが異なる FA分析コンテキスト

Slide 22

Slide 22 text

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 分析結果

Slide 23

Slide 23 text

COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. ソフトウェア開発の難しさ • 顧客業務・要求の理解 • データ分析と異なり、書いたコードは中⻑期的に動作する • 継続的に⼿を加え続けられる必要がある ドメイン分析プロセスで⽴ち向かう課題 23 Djangoの難しさ • ⾮常に強⼒なフレームワークだが、使いこなせていない感 • Djangoモデルが秩序なく利⽤される

Slide 24

Slide 24 text

ドメイン分析の結果をコードに反映させる

Slide 25

Slide 25 text

COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. ソフトウェア開発の難しさ • 顧客業務・要求の理解 • データ分析と異なり、書いたコードは中⻑期的に動作する • 継続的に⼿を加え続けられる必要がある ドメイン分析の結果をコードに反映させるで⽴ち向かう課題 25 Djangoの難しさ • ⾮常に強⼒なフレームワークだが、使いこなせていない感 • Djangoモデルが秩序なく利⽤される

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. アプリケーションのレイヤー設計との対応 36 アプリケーション層 ドメイン層 インフラストラクチャ層 リポジトリ (Repository)

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. ドメインモデルの実装指針 39 pydanticを利⽤します

Slide 40

Slide 40 text

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]

Slide 41

Slide 41 text

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]

Slide 42

Slide 42 text

COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. pydanticを利⽤したTokenクラスの実装 Pythonでドメインモデルを表現するには 42 from pydantic import BaseModel class Token(BaseModel): """単語クラス""" value: str class Config: frozen = True

Slide 43

Slide 43 text

COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. 1. 型ヒント( Type Hint )の恩恵を受けやすい 2. ⼊⼒値検証(Validation)がしやすい 3. pydanticが提供する豊富な型(EmaiStrやHttpUrlなど)を利⽤できる 4. dict()メソッドによる辞書型への変換が容易 ドメインモデルに pydantic を使うメリット 43 Python 標準だけで実装するならば @dataclass を活⽤しましょう

Slide 44

Slide 44 text

COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. Analysisクラスがフィールドに持つクラスもすべて永続化する 集約に対して Repository を実装する 44 リポジトリ (Repository) “リポジトリ” インフラストラクチャを隠蔽し、ドメイン オブジェクトの永続化と再構築する リポジトリの作成単位は集約とする

Slide 45

Slide 45 text

COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. Repositoryではインタフェースライクに抽象基底クラスを利⽤する Repository の実装指針 45 アプリケーション層 ドメイン層 インフラストラクチャ層 リポジトリ (Repository)

Slide 46

Slide 46 text

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オブジェクト。別のインスタンスとして返す """ ...

Slide 47

Slide 47 text

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オブジェクト。別のインスタンスとして返す """ ...

Slide 48

Slide 48 text

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オブジェクト。別のインスタンスとして返す """ ...

Slide 49

Slide 49 text

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のインターフェース設計では引数と返り値の 型をドメインオブジェクトとする

Slide 50

Slide 50 text

COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. Repository のインタフェースはドメイン層のクラスで表現される Repository の実装指針 50 アプリケーション層 ドメイン層 インフラストラクチャ層 リポジトリ (Repository)

Slide 51

Slide 51 text

COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. 実装クラスでは抽象クラスを継承し内部では Django モデルを利⽤して RDB にドメインモデルを永続化する 抽象クラスを継承した Repository の実装指針 51

Slide 52

Slide 52 text

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()

Slide 53

Slide 53 text

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 による別名インポート 抽象クラスの継承 実装メソッド

Slide 54

Slide 54 text

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モデルにマッピング

Slide 55

Slide 55 text

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へ永続化

Slide 56

Slide 56 text

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モデルをドメインモデルに変換して返す

Slide 57

Slide 57 text

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()

Slide 58

Slide 58 text

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)], )

Slide 59

Slide 59 text

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)], ) ドメインモデルからのファクトリメソッドの実装

Slide 60

Slide 60 text

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() で統⼀する

Slide 61

Slide 61 text

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)], )

Slide 62

Slide 62 text

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)], )

Slide 63

Slide 63 text

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)

Slide 64

Slide 64 text

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)

Slide 65

Slide 65 text

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モデルが秩序なく利⽤される

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. https://github.com/wf-yamaday/dllab-ddd-sample 本⽇のコードのリポジトリ 67

Slide 68

Slide 68 text

68 まとめ

Slide 69

Slide 69 text

COPYRIGHT INFORMATION SERVICES INTERNATIONAL-DENTSU, LTD. ソフトウェア開発の難しさ 今⽇お話したこと 69 Djangoの難しさ ドメイン分析 パターンを適⽤し、 コードに反映 DDD(ドメイン駆動設計) ü 顧客業務・要求の理解促進 ü 実装におけるDjangoの役割を明確化し、よりクリーンなコードへ

Slide 70

Slide 70 text

採⽤案内

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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 職種ごと応募ページ

Slide 73

Slide 73 text

新卒採⽤もやってます ISIDでは、「データサイエンス職」という新卒応募枠をご⽤意しています。データサイエンス枠で合格された ⽅は、AIトランスフォーメーションセンターへの配属が確約されます。興味がある⽅、是⾮ご応募ください。 問い合わせ先: 株式会社 電通国際情報サービス ⼈事部 新卒採⽤・インターンシップ担当 [email protected]