Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Go + Clean Architecture
Search
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
Mirrativ
April 14, 2022
Technology
2.8k
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Go + Clean Architecture
Mirrativ
April 14, 2022
More Decks by Mirrativ
See All by Mirrativ
Mirrativを支えるシンプルで堅牢な分析基盤
mirrativ
0
280
ライブ配信サービスの インフラのジレンマ -マルチクラウドに至ったワケ-
mirrativ
2
580
ミラティブ「採用候補者さまへの手紙」 / mirrativ letter
mirrativ
6
500k
デザイナーのお仕事(UI/UX GRAPHIC GROUP)
mirrativ
0
760
Go Conference 2023|Goのtestingパッケージにコミットした話
mirrativ
0
500
【ミラティブ】行動理念/行動指針/わかりあおうとし続けるガイドライン
mirrativ
2
36k
ミラティブエンジニア向け会社紹介資料/Engineer's Handbook
mirrativ
17
570k
動画配信サービスを支える基盤の内部
mirrativ
1
1.1k
【ミラティブ】24卒エンジニア向け_本選考説明資料
mirrativ
0
1.1k
Other Decks in Technology
See All in Technology
Oracle AI Database@AWS:サービス概要のご紹介
oracle4engineer
PRO
4
2.9k
なぜ Platform Engineering の土台に Kubernetes を選ぶのか
r4ynode
1
570
2026TECHFRESH畢業分享會 - 葬送的通靈師:化系統與用戶雜訊成行動訊號
line_developers_tw
PRO
0
740
地球に⽣きるAI —GeoAIと「中間領域」— / AI Living on Earth — GeoAI and the “Intermediate Layer” —
ykiyota
0
270
MCP Appsを作ってみよう
iwamot
PRO
4
510
データサイエンスを価値につなげるプロジェクト設計 〜 DS一年目が現場で得た気づき 〜
ysd113
1
160
脆弱性対応、どこで線を引くか
rymiyamoto
0
360
【Cyber-sec+】経営層を"動かす"ための考え方
hssh2_bin
0
120
AI-DLCを活用した高品質・安全なAI駆動開発実践 / AI Driven Development with AI-DLC
yoshidashingo
0
170
Claude Code の Sandbox 機能を Anthropic Sandbox Runtime(srt) で試そう!/lets-play-anthropic-sandbox-runtime
tomoki10
1
530
AAIFに入ってみた ~内から見えるコミュニティ動向~
sato4
0
140
Agent Skills設計で柔軟性と硬さのバランスが難しい話
nassy20
0
120
Featured
See All Featured
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
31
2.8k
HU Berlin: Industrial-Strength Natural Language Processing with spaCy and Prodigy
inesmontani
PRO
0
410
brightonSEO & MeasureFest 2025 - Christian Goodrich - Winning strategies for Black Friday CRO & PPC
cargoodrich
3
730
Paper Plane (Part 1)
katiecoart
PRO
0
8.8k
New Earth Scene 8
popppiees
3
2.3k
Gemini Prompt Engineering: Practical Techniques for Tangible AI Outcomes
mfonobong
2
430
BBQ
matthewcrist
89
10k
Balancing Empowerment & Direction
lara
6
1.2k
Code Review Best Practice
trishagee
74
20k
"I'm Feeling Lucky" - Building Great Search Experiences for Today's Users (#IAC19)
danielanewman
231
23k
Avoiding the “Bad Training, Faster” Trap in the Age of AI
tmiket
0
170
Getting science done with accelerated Python computing platforms
jacobtomlinson
2
220
Transcript
ミラティブのサーバサイドを Go + Clean Architectureで 再設計した話 2022.04.14 Sumihiko Natsu Mirrativ,
Inc. © 2022 Mirrativ, Inc. STRICTLY CONFIDENTIAL
▪ 夏 澄彦 ▪ 2015年 DeNA新卒入社 • 入社とほぼ同じタイミングで社内でMirrativプロジェクト始動 ▪ 事業部側のテックリード
兼 バックエンドの基盤開発 • つい先日Go 1.18を本番投入 自己紹介
Mirrativの技術コンポーネント 今日はこちらのお話
バックエンドから見たMirrativ ▪ 配信サービスとしてのMirrativ • 視聴者から配信へのギフト • 配信者へのコメント • おすすめ配信 ▪
SNSとしてのMirrativ • フォロー・フォロワー • チャット • タイムライン ▪ ゲームとしてのMirrativ • ランキング • ガチャ • ライブゲーム • リアルタイム性/即時性が高い • データ量が多い • アクセスの時間的局所性が高い
▪ アーキテクチャが崩れかけている • MVC + Serviceだが、依存関係がスパゲッティ ▪ 膨れ上がるModel • データ型
+ 永続化 + Presenter • SQLをORMにより、様々なレイヤーから発行される • 果てはViewからも。。。 • 負債が溜まったテーブルを再設計しづらい ▪ Contextという名のもとにあらゆるレイヤーから密結合を 黙認されているGodなクラス Clean Architectureへ移行しようとした背景 開発開始から6年半近く経過しているプロダクト
Clear Architectureを導入 ▪ 「コンポーネントの依存性を一方向にする」を最重要視 • 外側の実装を修正した際に内側への影響を最小化したい • 外側の依存性を内側に注入することで、モックを差し込みやすくしたい • 各レイヤーの責任と依存・入力・出力を明確化する
▪ まずは既存のPerlコードにClean Architectureを適用して、現実世界 の処理をレイヤリングできるか確認 ▪ 本家本元のClean Architectureとは異なる場合がありますが、ご了承 ください
Entity package entity type UserID uint64 type User struct {
UserID UserID Name string } type Logger interface { Error(ctx context.Context, err error) Log(ctx context.Context, level LogLevel, label string, payload ...interface{}) } ▪ オブジェクトでビジネスロジックを表現する責務 ▪ Loggerのように全レイヤーから参照される interfaceなどもEntitiesに存在 • 実装はInfra層
UseCase Entity / Repositoryを使い、ユースケースを達成する責務 ▪ トランザクションのスコープを管理するのもお仕事の1つ package user type interactor
struct { txm repository.TransactionManager repoUser repository.User } func (i interactor) UpdateRecommend(ctx context.Context, now time.Time) error { // おすすめユーザを計算 return i.txm.Do(ctx, func(ctx context.Context) error { return i.repoUser.UpdateRecommend(ctx, recommendUserIDs) }) }
Repository ▪ データの集約、永続化の責務 • 対応するDataSourceを活用し、UseCaseレイヤー が実際のテーブル構造などを把握しなくてもEntity の永続化を行える責務を負う ▪ データの整合性が取れる最小単位 •
例)MySQL側のDataSourceを更新したら、 Memcached側のDataSourceも更新 ▪ DataSourceで取得したデータをEntityに変換 ▪ CRUDなinterfaceを提供 • 命名規則もCreate/Read/Update/Deleteを強制
Repository package user type user struct { dsmemcachedRecommendUsers dsmemcached.RecommendUsers dsmysqlRecommendUser
dsmysql.RecommendUser } func (r user) ReadRecommend(ctx context.Context) ([]entity.User, error) { // dsmemcachedRecommendUsersからおすすめユーザを取得 // なければdsmysqlUserから問い合わせ // 取得したDataSource固有の構造体をEntityへ変換 } func (r user) UpdateRecommend(ctx context.Context, userIDs []entity.UserID) error { // dsmysqlRecommendUserで更新してから、dsmemcachedRecommendUsersを更新 }
Repository Transaction ▪ 複数のDataSourceへのトランザクションを管理する責務 ▪ 複数のデータベースへの書き込みがある場合は、 すべての処理 が完了してからのcommitやエラー時のすべてのsql.Txの rollbackなどを抽象化 //
commitとrollbackができるものをTransactionと定義 type Transaction interface { Commit(ctx context.Context) error Rollback(ctx context.Context) error } // 複数のTransactionを抽象化し、同一データベースへの Transactionはキャッシュ type Transactions interface { Get(key string, builder func() (Transaction, error)) (Transaction, error) // cache更新などrollbackできない処理を登録し、全てのcommitが成功した場合のみ実行 Succeeded(f func() error) } // トランザクションのスコープを管理するオブジェクト( dry-run時は最後に全てrollback) type TransactionManager interface { Do(ctx context.Context, runner func(ctx context.Context) error) error }
DataSource ▪ Infraを活用し、Repositoryが要求するデータの取 得、永続化を達成する責務 • MySQLのtableや、Memcachedのkey、 Elasticsearchのtypeと1:1の関係 ▪ 該当するミドルウェア固有の操作名に沿った命名規則 type
ds struct { infraMySQL infra.MySQL } func (ds ds) Update(ctx context.Context, users []*dsmysql.RecommendUserRow) error { txn, err := ds.infraMySQL.GetTxn(ctx, "BASE_W") // BASE_W はデータベース系統の名前 _, err = txn.ExecContext(ctx, "delete from recommend_user") _, err = txn.ExecContext(ctx, "insert into recommend_user ...") return err } func (ds ds) Select(ctx context.Context, limit int) ([]*dsmysql.RecommendUserRow, error) { return ds.selectRecommendUsers(ctx, limit, repository.DB_R) }
DataSource ▪ dsmysql.RecommendUserRow の構造体や selectRecommendUsers の処理などは、以下のような内製の テーブル定義から自動生成 • kyleconroy/sqlc をオマージュ
▪ このテーブル定義からDDLを生成し、 k0kubun/sqldef に食べ させることで、MySQLのマイグレーションなども実行 recommend_user: columns: - name: user_id type: uint64 foreign_key: user.user_id - name: score type: uint8 primary_keys: - user_id indexes: - columns: - score queries: - sql: select * from recommend_user order by score desc limit :limit
Infra ▪ ミドルウェアとの実際の接続や入出力などを担当 ▪ 内側のレイヤーが各ミドルウェアのI/Fを把握せず とも利用できる状態にする責務を負う type Transaction interface {
repository.Transaction DB } type MySQL interface { // 複数系統のデータベースが存在するので aliasで指定する Get(ctx context.Context, alias string) (DB, error) Txn(ctx context.Context, txns repository.Transactions, alias string) (Transaction, error) } type DB interface { ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) }
▪ 外界からの入力をControllerへルーティングする責務 ▪ 時刻情報も外界の一部としてみなし、このレイヤー以外 では現在時刻を取得しないように制限 • これにより特別なライブラリを用いずともテストを決定的 にしたり、動作確認する際に任意の時刻への変更を行いや すくする Frameworks
▪ HTTP RequestのURLなどを参照し、該当する ControllerへRequestを渡す • Sessionの解決やCSRFの検証などもこのレイヤー • RequestやResponseがOpenAPIの定義通りかどう かを検証 •
実行速度が犠牲になるので開発環境のみ Frameworks Web
▪ queueベースで動作している非同期処理の場合 は、dequeueする処理とdequeueされたイベント をControllerをつなげる ▪ それ以外の非同期処理の場合は、指定された頻度 でControllerを実行する Frameworks Daemon
Controller package user type Controller struct { user inputport.User }
func (c Controller) RecommendUsers(ctx context.Context, webCtx *web.Context) error { recommendUsers, err := c.user.ReadRecommendUsers(ctx) webCtx.RenderJSON(ctx, map[string]interface{}{ "users": presenters.Users(recommendUsers), }) return nil } ▪ 外界からの入力を、達成するユースケースが求める入力に変換 する責務 • HTTP Request内のパラメータを取り出したり、queueの中から必 要な情報を取り出して適切なInteractorに渡したり • また、ミラティブではInteractorから返ってきたEntityを Presenterで変換し、外界が求める出力に変更するのもController の責務
テスト戦略 ▪ 基本的なカバレッジ率の達成にはユニットテストを用い、統合テスト では正常系のユースケースのみ検証 ▪ CIでテストのカバレッジ率が90%以上であることを検証 • go tool cover
で出力されるカバレッジ率ではなく、エラーハンドリング だったり、delegateのように引数を一切加工せずにフィールドに渡すだけ の関数は除外
▪ stringer, gomock, quicktemplate, wireを利用 ▪ SQLからMySQL用のDataSourceのコードを生成 ▪ wireの定義も自動生成 ▪
Table Driven Test用のboilerplateも自動生成 Generator
▪ golangci-lintをベースにいくつか内部で自作 ▪ 整数オーバーフローのチェック • 専用のutilityを利用するよう強制 ▪ 命名規則の強制・関数の順番(公開 => 非公開で辞書順)
▪ t.Parallelの強制 ▪ time.Now()の使用の制限 Lint
▪ 新規機能はほぼGoで開発 ▪ 既存機能のGo移行が今後の課題 ▪ サービスや組織の成長に合わせて、生産性を最大化するためのより良 いアーキテクチャを模索し続けられるエンジニアを募集中!! Go移行の進捗と今後の展望 Perl Go