Slide 1

Slide 1 text

Golangを使った バックエンドの実装⼊⾨ 2024.7.20 AWS事業本部ソリューション部モダンアプリコンサルチーム ⾨別 優多

Slide 2

Slide 2 text

Xへの投稿の際は、 ハッシュタグ #devio2024 でお願いいたします。 2 お願い

Slide 3

Slide 3 text

Agenda 3 ● gRPC ● Protocol Buffer ● connect-go ● connect-query(React Query) ● LayeredArchitecture ● DI ● Microservices ● (おまけ)CQRS+Event Sourcing

Slide 4

Slide 4 text

45分でGolangを使った Backendの雰囲気を 理解出来るようになる! 4

Slide 5

Slide 5 text

5 (Golangはやれば誰でも出来る と思うので) ⾔語 < 周辺勘所メインで話します

Slide 6

Slide 6 text

gRPC ● Googleが開発したRPC(Remote Procedure Call) ○ HTTP/2上で実装されたプロトコル ○ メソッドを呼び出すような感覚でBackendを実装する ○ ストリーム、双⽅向通信など幅広く実装可能 ● データモデルの定義が簡単にできるため、⾮常に便利 ○ Microservicesとしても統括的に管理することが可能 6

Slide 7

Slide 7 text

● こちらもGoogleが開発したスキーマ⾔語 ● JSON Schemaも良いが、⼿動で書くのは厳しい‧‧‧ ○ プログラミング⾔語から⽣成なども出来るが、、、 ● Protocol Buffer ○ 宣⾔的に簡単に書くことが出来て書きやすい ○ gRPCのメソッドを定義する事ができる Protocol Buffer 7

Slide 8

Slide 8 text

● Protocol Bufferから各⾔語向けにServerのInterfaceと Clientが⽣成できる ○ めっちゃ精度良い ■ Backendは⽣成されたInterfaceをImplement ■ 使⽤側はClientをImportしてCallするだけ ● 良くも悪くもRESTFulな設計とは別の考え⽅が出来る ○ RPCなので単純なCRUD以外の要件などにも対応 ○ Validation含めて、Protocol Bufferのエコシステムで対応 が出来る RESTと異なる点 8

Slide 9

Slide 9 text

Protocol Buffer実装例 9

Slide 10

Slide 10 text

Protocol Buffer実装例 10

Slide 11

Slide 11 text

Protocol Buffer実装例 11

Slide 12

Slide 12 text

Protocol Buffer実装例 12

Slide 13

Slide 13 text

Backendの実装 13

Slide 14

Slide 14 text

Clientの実装 14 gRPC Clientが⽣成されるので、 これを呼び出してあげればOK

Slide 15

Slide 15 text

フロントからの実装⽅法 15 connect-query connect-query-es connect-queryを使うことで、 フロントエンドからEnvoy等無 しで直接gRPC実装のサーバーに 疎通できる。 ClientはTypeScript向けにも⽣ 成可能。TanStackQueryとほぼ 互換のある実装が可能

Slide 16

Slide 16 text

フロントからの実装⽅法 16

Slide 17

Slide 17 text

DI 17

Slide 18

Slide 18 text

⼿動DI 18

Slide 19

Slide 19 text

uber-go/fx 19

Slide 20

Slide 20 text

uber-go/fx 20 usecase.go main.go

Slide 21

Slide 21 text

要求したInterfaceに 沿ったものを ⾃動で取ってこれる! 21

Slide 22

Slide 22 text

Layered Architecture 実装例 22

Slide 23

Slide 23 text

・Handler  ・外向けのHandler ・Usecase  ・ユースケース  ・今回はDIの例を出すためあえてcreate/getで別けてる ・Service  ・ドメイン層からドメインオブジェクト作成   ・Repositoryに渡す ・Domain  ・ドメインロジックの実装 ・Infrastructure  ・DBの定義やgRPCサーバー  ・今回はpersistenceはメモリ上のmap[string]interface{} Layered Architecture 実装例 23

Slide 24

Slide 24 text

DEMO 24

Slide 25

Slide 25 text

Microservices 25

Slide 26

Slide 26 text

Microservice 26 gRPCとMicroservicesは相性が良い ● パフォーマンス上のメリットももちろんあるが ○ Protocol Bufferのエコシステムが最強 ■ Server, Clientなど実装が楽 ■ 各⾔語向けにも対応 ■ Protocol Bufferの破壊的変更も検知可能 ■ 各サービス毎にバラバラの実装をするより、gRPC, connet-goで喋る プロトコルを定めた⽅が楽 ○ 各チームで分散して効率良く開発していく所において、ベストな選択 (個⼈的感想)

Slide 27

Slide 27 text

Microservicesのつらみ 27 多くの利点がある一方で

Slide 28

Slide 28 text

Microservicesのつらみ 28 多くの利点がある一方で ・サービス間通信 ・トランザクションの管理 ・SAGAパターン ・サービスが増えてきたときの開発と運用の負荷 ・新規機能追加に伴う各サービス、チームとの調整 ・依存性の無いデプロイ ・認証、認可 ・インフラ設計 ・監視、トレーシングの設計

Slide 29

Slide 29 text

Microservicesのつらみ 29 多くの利点がある一方で ・サービス間通信 ・トランザクションの管理 ・SAGAパターン ・サービスが増えてきたときの開発と運用の負荷 ・新規機能追加に伴う各サービス、チームとの調整 ・依存性の無いデプロイ ・認証、認可 ・インフラ設計 ・監視、トレーシングの設計

Slide 30

Slide 30 text

完全にコンテキストが異なる物は良いと思う ● ユーザーの統括的なサービスなど、別軸として分離できる コンテキスト ● チームとして完全に分離しているなど、別軸で開発が進められてい る ● メンテナスしていく余力が会社の開発の組織的にある場合 ● 将来的に大量のサービスにならなそうなのであれば、まずは Moduler Monolith的な実装でInterfaceを区切って実装するのが良 い(個人的感想) Microservices化するべきか 30

Slide 31

Slide 31 text

まとめ 31

Slide 32

Slide 32 text

まとめ ● Golangとそれに付随するgRPC, Protocol Bufferのエコシス テムは凄い! ● Backendエンジニアとしてこれまでの知識があれば、 GolangでBackendを実装するのはそこまでハードルは⾼く ない ● 「gRPC、なんか敷居⾼そうRESTでいいや」から 「gRPC、割と簡単そうじゃん」と思っていただけると! 32

Slide 33

Slide 33 text

おまけ 33

Slide 34

Slide 34 text

CQRS+Event Sourcing 34

Slide 35

Slide 35 text

・・・の前に、DDDの 実装的な側⾯を振り返り 35

Slide 36

Slide 36 text

● ざっくり解説 ○ ビジネスドメインを軸とした開発⼿法と組織論 ○ Entity, ValueObjectとしてドメインを定義 ■ ユーザー、タスク、コメントなど ○ ドメイン知識をドメイン層で定義‧処理を⾏い、 正しいインスタンスしか存在出来ない状態とする ○ 今回は実装的な側⾯でしか話しませんが ■ 本来は1時間で収まりきらないくらいの物量 DDD(Domain-driven design) 36

Slide 37

Slide 37 text

● 実装的な側⾯ ○ ドメイン層は古典的なオブジェクト指向として実装され ることが多い ■ task := new Task(props) ■ task.Update(props) ● → Taskの更新条件を満たしているのか等のロ ジックを、ドメインオブジェクトで実装する ○ 永続化はRepository層として実装して、Repository層は DB等への保存/取得と、ドメインオブジェクトを⽣成し て返す DDD(Domain-driven design) 37

Slide 38

Slide 38 text

DDD(Domain-driven design) 38

Slide 39

Slide 39 text

DDD(Domain-driven design) 39

Slide 40

Slide 40 text

DDDにおける参照の課題 ● 参照の課題 ○ 従来のDDDなRepository層を使⽤する場合、ドメイン の⽣成もセットで処理されるため、不要なデータまで 処理される ○ 参照のためだけに、ドメインの内部情報を公開する必 要がある ○ N+1な実装をしてしまうと、パフォーマンス上問題が ある 40

Slide 41

Slide 41 text

CQRSとは? Command Query Responsibility Segregation コマンド‧クエリ責任分界 「コマンド」と「クエリ」で責任を分離する 41

Slide 42

Slide 42 text

CQRSとは? Command Query Responsibility Segregation コマンド‧クエリ責任分界 「コマンド」と「クエリ」で責任を分離する 「書き込みと読み取りを分離する」は、 実は既にやられている⽅も多いとは思う 42

Slide 43

Slide 43 text

“In the “Stereotypical Architecture” the domain was handling both Commands and Queries, this caused many issues within the domain itself.” 「ステレオタイプなアーキテクチャ」では、ドメインがコマンドと クエリの両⽅を処理しており、これがドメイン内に多くの問題を引 き起こしていた。 CQRSとは? 43 https://cqrs.wordpress.com/wp-content/uploads/2010/11/cqrs_documents.pdf

Slide 44

Slide 44 text

CQRSとは? “Once the read layer has been separated the domain will only focus on the processing of Commands. These issues also suddenly go away. Domain objects suddenly no longer have a need to expose internal state, repositories have very few if any query methods aside from GetById, and a more behavioral focus can be had on Aggregate boundaries” リード層を分離したら、ドメインはコマンドの処理にのみ専念することになる。これによ り、いくつかの問題も突然解消される。ドメインオブジェクトは内部状態を公開する必要が なくなり、リポジトリはGetById以外のクエリメソッドをほとんど持たなくなり、アグリゲー トの境界に関してより⾏動に焦点を当てることができるようになる。 44 https://cqrs.wordpress.com/wp-content/uploads/2010/11/cqrs_documents.pdf

Slide 45

Slide 45 text

● Command ○ ビジネスロジックを処理して、Entityを適切に処理する事に集中 ○ 書き込み結果等レスポンスは返さない ● Query ○ ドメインロジックは無く、データの取得に特化する ○ ドメイン層を経由せず、直接データをDTO(Data Transfer Object) に沿った値として直接取得する(ドメイン知識は不要) ○ ドメイン層におけるRepositoryとは別にQueryServiceを定義して 直接クエリする Command/Query 45

Slide 46

Slide 46 text

CQRS - Query DDDの場合、集約を跨いだ複雑な物を取得したい時、複数の Repositoryを参照する必要があり、N+1になりがち 例) task := taskRepo.FindTaskBy(id) user := taskRepo.FindByUserId(task.UserId) blog := blogRepo.FindByUserId(user.Id) 46

Slide 47

Slide 47 text

CQRS - Query QueryServiceを定義して、お好みなDTO(Data Transfer Object)に沿ったUseCase層からQueryServiceを呼び出して 返却する ドメイン層が処理せずそのままDTOを返却する(だいじ) 「この情報も欲しいんだよね」みたいなのにも容易に対応が 可能。 47

Slide 48

Slide 48 text

CQRS - Query QueryServiceを定義して、お好みなDTO(Data Transfer Object)に沿ったUseCase層からQueryServiceを呼び出して 返却する ドメイン層が処理せずそのままDTOを返却する(だいじ) 「この情報も欲しいんだよね」みたいなのにも容易に対応が 可能。 48 単純な “QueryService” としては、 これだけのシンプルな話。

Slide 49

Slide 49 text

Event Sourcing(ES) 状態の変化を、イベントとして保存する 状態の変化=コマンドを処理した結果のイベントを記録する イベントを再⽣することで、Entityの現在の結果を再現する ことができる CQRSとEvent Sourcingは必ずしもセットという訳では無い Event Sourcingとは? 49

Slide 50

Slide 50 text

Event Sourcing 50 ● CQRS + ESでは、従来のCURDのような永続化されたステートを元 にした設計ではない ● Commandとして変更内容/処理内容を定義して、EventStoreに シーケンシャルに保存する ● 「変更内容/処理内容」をEventStoreから取得して、古い順から再 ⽣することで、Entityの現在の状態を知ることが出来る

Slide 51

Slide 51 text

CQRS(w/ES)とは? 51 https://docs.aws.amazon.com/ja_jp/prescriptive-guidance/latest/modernization-data-persistence/cqrs-pattern.html

Slide 52

Slide 52 text

Event Eventは過去に起こった出来事を発⾏する。ユースケースで分けると 良い。 CreatedTaskEvent -> タスクを作成する AssignedTaskEvent -> タスクのアサインを変更する CompletedTaskEvent -> タスクを完了とする MovedTaskToAnotherProjectEvent -> 他のプロジェクトに移動 52

Slide 53

Slide 53 text

Event sourcing - Event 53 { "event": "CreatedTask", "version": "1", "payload": { "taskId": "123e4567-e89b-12d3-a456-426614174000" , "title": "Implement CQRS", "description": "Create and implement CQRS architecture for the project" , }, "timestamp": "2024-01-01T00:00:00Z" } CreatedTaskEventの例

Slide 54

Slide 54 text

Event sourcing - Event 54 { "event": "AssignedTask", "version": "2", "payload": { "taskId": "123e4567-e89b-12d3-a456-426614174000" , "assign": "2b7025e3-199e-403e-b3a6-4aa9413e7028" , }, "timestamp": "2024-01-01T00:00:00Z" } AssignedTaskEventの例

Slide 55

Slide 55 text

Command側は適切なソースを元に、Commandを処理(ドメインロ ジックを実⾏)してEventを作成する。 これらのイベントを順番にEvent storeに保存する。 なお、Event storeは排他制御、整合性の配慮が必要。なぜならば、イ ベントは順番に実⾏されるため、不整合が起こるとデータの再現が出 来ないため。 Event sourcing - Command 55 seq: 0 CreatedTask seq: 1 AssignedTask seq: 2 ChangeStatus seq: 3 CompletedTask

Slide 56

Slide 56 text

イベントを古い順から実⾏(再⽣)して最新のデータを作成する。 先程の例) CreatedTask→payloadのデータを元に作成 AssignedTask→payloadを元にtaskIdのAssignを変更 ChangeStatus→payloadを元にtaskIdのstatusを変更 CompletedTasks→タスクを完了状態に変更 Event sourcing - Query 56 seq: 0 CreatedTask seq: 1 AssignedTask seq: 2 ChangeStatus seq: 3 CompletedTask

Slide 57

Slide 57 text

つまり 57 ● Commandの責務はユースケース別のコマンドを処理し て、ドメインロジックに集中する ○ ドメインロジックに基づいて処理を⾏う ○ その結果をイベントストアに保存 ● イベントストアに保存された値を元に、Snapshotを作成 する ○ 例)DynamoDB Streams, Pub/Sub等 ● Queryの責務はユースケース別のDTOを取得して返却 ○ 都合の良いデータを取得して、返却する事に集中

Slide 58

Slide 58 text

CQRSまとめ 58

Slide 59

Slide 59 text

CQRS, ESがマッチするプロジェクト ● CQRS ○ ビジネスロジックが複雑で、CRUDでの実装が苦しい場合 ○ N+1となり苦しい場合 ○ 柔軟にReadのレスポンスを変えたい、複雑なクエリが必要等、Readに関 して様々な要件がある場 ● Event Sourcing ○ 監査等で過去の状態、変更ログ等を保持‧分析する必要がある場合 ○ 複数サービスと⽂字通りのイベントドリブンな連携をしたい場合 マッチするプロジェクト 59

Slide 60

Slide 60 text

使う必要が無いプロジェクト ● CQRS ○ ドメインがシンプル、複雑なビジネスロジックが無い場合 ■ 既存のRepository層実装でも問題が無い場合等 ● 参照箇所が複数⽣まれるため、煩雑になる可能性あり ● Event Sourcing ○ 厳密な変更履歴を保持する必要が無い場合 ○ Repository層を通さず、Infra層でDTOを実装等、過去のイベント保 持無しでも解決が出来る場合 使う必要が無いプロジェクト 60

Slide 61

Slide 61 text

61