アメブロの大規模システム刷新と それを支えるSpring
-
Upload
takuya-hattori -
Category
Engineering
-
view
13.523 -
download
0
Transcript of アメブロの大規模システム刷新と それを支えるSpring
CyberAgent, Inc. All Rights Reserved
アメブロの大規模システム刷新とそれを支えるSpring
1
自己紹介
向井 政貴
株式会社サイバーエージェント
サーバサイドエンジニア
アメーバブログの開発・運用を担当
最近会社の料理部が楽しい
2
2015年の状況
システム刷新
まとめ
アメーバブログとは
3
CyberAgent, Inc. All Rights Reserved
アメーバブログ
4
アメーバブログ
PC、スマホ、ガラケー、アプリ
ランキングなど周辺サービス
5
アメーバブログ
2004年9月サービス開始
約12年続くサービス
6
CyberAgent, Inc. All Rights Reserved
2015年アメブロの状況
7
古いバージョンの技術
8
MySQL 4系
CentOS 4系
java 1.5 ~ 1.7
9
複数のプロダクトに
似たような処理
10
ブラウザから記事投稿できるよねアプリからも記事をかけるようにしたい!
同じ機能だけどAPIとして新しく作るか・・
アメブロ
記事投稿
API
記事投稿
11
API
ブラウザから読者登録できるよねアプリからも読者登録したい!
アメブロ
記事投稿
API
記事投稿
読者登録 読者登録
同じ機能だけどAPIとして新しく作るか・・
12
API API
調査用に記事情報を取得したい!
アメブロ
記事投稿
API
記事投稿
読者登録 読者登録
同じ機能だけどAPIとして(ry
記事取得 記事取得
13
フレームワークの混在
14
Struts 1.x , S2Struts, Spring 3.x
● Struts から Spring3 に移行しようと頑張った
形跡はある…
● ややこしい
● 学習コスト
15
苦労の多い動作確認環境
16
動作確認方法の歴史
2013年
.class
SCP!!!!
ビルド
.class
チーム共用ステージングサーバ
17
動作確認方法の歴史
2013年
2014年 個人開発用サーバ
PUSH
DEPLOY
18
動作確認方法の歴史
2013年
2014年 個人開発用サーバ
2015年 ココ
19
失われた思想(?)
20
実際のコード例:記事データ検索メソッド
public void search(boolean showEntryTextFlag) {
!? !?
21
いろいろあるけど大まかに言うと
22
保守性の低下
増大していく開発コスト
ビューとビジネスロジックの強い依存関係
刷新前システムの課題
23
CyberAgent, Inc. All Rights Reserved
さらに
24
CyberAgent, Inc. All Rights Reserved_人人人人人人人人人人人_> データセンター移設 <
 ̄Y^Y^Y^Y^Y^Y^Y^Y^Y^ ̄
25
アメブロのシステムの大部分が
稼働していたDCから撤退
26
保守機材の在庫が限界
27
LBやコアスイッチの調子が悪化
28
1. 既存システムのまま移設する
2. 作り直して移設する
選択肢
29
どちらにするか
30
作り直すとして間に合うのか
31
何か障害が起きないか
32
怖い
33
刷新前のシステムつらい
34
面白くない
35
このままだと新しい人が入ってこない
36
世間の技術トレンドから遅れる
37
開発速度は遅くなる一方
38
39
CyberAgent, Inc. All Rights Reserved
システム刷新だ
40
システム規模
41
40超
数百台
刷新後のあるAPIサーバ
へのリクエスト数 60万リクエスト/m
サーバ台数
プロジェクト数
5300万人(2016年8月時点)会員数
ページがみれないと広告が表示されないため売上に直撃
社内他サービスへの影響
芸能事務所から問い合わせ
システムになにかあると...
42
CyberAgent, Inc. All Rights Reserved
システム刷新の対象
43
44
フロントエンド
バックエンド
CyberAgent, Inc. All Rights Reserved
システム刷新の要件
45
障害なし
長期のサービス停止は回避
DC移設までに完遂
画面や機能の仕様を維持
開発コスト減少
一般的なアーキテクチャ
MUST WANT
46
47
CyberAgent, Inc. All Rights Reserved
Why Spring?
48
低い導入コスト
開発陣の慣れ
テンプレートの使い回し
刷新前のシステムにSpringの採用実績
49
ローカル開発環境
開発コストの削減
ローカル開発による生産性の向上
50
@Controller, @Service, @Repository
共通の認識があるレイヤードなアーキテクチャ
アーキテクチャを統一しやすい
51
CyberAgent, Inc. All Rights Reserved
システム刷新
52
保守性の低下
増大していく開発コスト
ビューとビジネスロジックの強い依存関係
刷新前システムの課題(再掲)
53
保守性の低下
増大していく開発コスト
ビューとビジネスロジックの強い依存関係
従来システムの課題
54
CyberAgent, Inc. All Rights Reserved
保守性の低下
55
処理が適切な粒度でまとまっていない
56
刷新前システムの課題
アメブロ
ORM
ビジネスロジック
57
人によって処理を書く場所がバラバラ
再利用性や可読性が低い
刷新前のシステム
レイヤードアーキテクチャ
58
刷新後システムの設計
Controller
Facade
Service
Repository
リクエストを受けて結果を返す
一操作に必要なServiceの呼び出し
最低限、まとめておくべきビジネスロジック
DBアクセスやAPIの呼び出し
59
ポリシーの統一化
再利用性、可読性向上
メンテされているのかわからない
APIドキュメント
刷新前システムの課題
60
wikiにAPIドキュメント用意
手動でメンテナンス
実際のソースコードとの整合性が不安
61
Springfox + Swaggerによる
自動ドキュメント化
62
https://github.com/springfox/springfox
https://github.com/swagger-api/swagger-ui
63
@RequestMapping( method = RequestMethod.GET, value = "/v1.0/public/blogger/{amebaId}" ) public BloggerResponse getBlogger ( @PathVariable("amebaId") String amebaId ) { return bloggerFacade.getBlogger(amebaId); }
64
エンドポイントを自動的にドキュメントに反映
複雑なドキュメントはSwaggerのアノテーションが必要
エンドポイントのきれいな一覧だけでもだいぶありがたい
65
ここまでで25分ぐらい
66
保守性の低下
増大していく開発コスト
ビューとビジネスロジックの強い依存関係
刷新前システムの課題
67
自己紹介
服部 拓也
2013年 株式会社サイバーエージェント入社
入社以来アメーバブログのサーバーサイドを担当
現在はディレクションとマネジメントを兼務
マイブームは喘息
68
CyberAgent, Inc. All Rights Reserved
69
開発コスト
70
ローカル開発環境ない ・
動作確認方法の歴史 (再掲)
2013年
2014年 個人開発用サーバ
2015年 ココ
71
72
73
ローカル開発環境構築が容易
フロントエンドエンジニアでも簡単
-> 開発速度UP
74
プロダクトごとに
リポジトリが分散
75
76
77
ソースコードもリリースジョブも
再利用性が低い
削減できるはずのコスト
78
リポジトリの統合 ・
79
マルチプロジェクト化
80
81
リリースジョブを共通化できる
処理の見通しがよくなる
処理を共通化しやすくなる
刷新後システムの処理共通化
API βAPI α
Controller
Facade
Service
Repository
Controller
Facade・・・
82
マルチプロジェクト構成によるコードの共通化
リリースジョブの統一化
-> 開発効率UP
83
CyberAgent, Inc. All Rights Reserved
だがしかし…
84
まとめたことによって
不要なのにBean化されてしまうクラスが増大
85
86
起動のためだけに
不要なBeanの設定が必要
まとめたことによってBean化されるクラスが増大
リポジトリ
Project A
Project B
Service Project Repository Project
87
まとめたことによってBean化されるクラスが増大
facadeservice B
repository B
facade
service A
service C
service D
repository A
repository C
Project A
Project B
88
リポジトリ
まとめたことによってBean化されるクラスが増大
facadeservice B
repository B
facade
service A
service C
service D
repository A
repository C
リポジトリ
Project A
89
まとめたことによってBean化されるクラスが増大
facadeservice B
repository B
facade
service A
service C
service D
repository A
repository C
リポジトリ
Project A
90
どうしたか
まずは Service
91
FacadeクラスがAutowiredしているServiceクラスだけを
Bean化しよう!
● @Facadeアノテーションを作った
● @FacadeのついたクラスをBean化して、
そのFacadeクラスがAutowiredしているServiceクラスだけを
Bean化するInitializerを作った
92
@ComponentScan( excludeFilters = { @ComponentScan.Filter( type = FilterType.ANNOTATION, value = Facade.class ), @ComponentScan.Filter( type = FilterType.ANNOTATION, value = Service.class ) })
適用例
public class Main { public static void main(String[] args) { new SpringApplicationBuilder(Main.class) .initializers( new FacadeInitializer() ) .run(args); }}
93
まとめたことによってBean化されるクラスが増大
facadeservice B
repository B
facade
service A
service C
service D
repository A
repository C
リポジトリ
@Facade
@Service
@Service
repository D
@Service
@Service
Project A
94
まとめたことによってBean化されるクラスが増大
facadeservice B
repository B
facade
service A
service C
service D
repository A
repository C
リポジトリ
@Facade
@Service
@Service
repository D
@Service
@Service
95
Project A
まとめたことによってBean化されるクラスが増大
facadeservice B
repository B
facade
service A
service C
service D
repository A
repository C
リポジトリ
@Facade
@Service
@Service
repository D
@Service
@Service
96
Project A
どうしたか
Repository編
97
プロダクト毎に接続先DBの情報をapplication.ymlに宣言する
● 宣言した接続先に関連するRepositoryだけをBean化する
Initializerを作った
98
まとめたことによってBean化されるクラスが増大
A repository B repository
C Datasource
C repositoryA repository B repository
C repository
application.ymlInitializer
99
100
jdbc: blog: // [HOST] + [SCHEME] の組み合わせにユニークなネームスペースをつけたイメージ
master: url: jdbc:mysql://[HOST]/[SCHEME]?zeroDateTimeBehavior=convertToNull&... initialSize: 1 maxTotal: 1 ... slave: url: jdbc:mysql://[HOST]/[SCHEME]?zeroDateTimeBehavior=convertToNull&... initialSize: 1 maxTotal: 1 ... entry: slave: url: jdbc:mysql://[HOST]/[SCHEME]?zeroDateTimeBehavior=convertToNull&... initialSize: 1 maxTotal: 1 ...
application.yml
@ComponentScan( excludeFilters = { @ComponentScan.Filter( type = FilterType.ANNOTATION, value = Repository.class ), @ComponentScan.Filter( type = FilterType.ANNOTATION, value = Facade.class ), @ComponentScan.Filter( type = FilterType.ANNOTATION, value = Service.class ) })
public class Main { public static void main(String[] args) { new SpringApplicationBuilder(Main.class) .initializers( new RepositoryInitializer(), new FacadeInitializer() ) .run(args); }}
101
適用例
まとめたことによってBean化されるクラスが増大
facadeservice B
repository B
facade
service A
service C
service D
repository A
repository C
リポジトリ
repository D
102
まとめたことによってBean化されるクラスが増大
facadeservice B
repository B
facade
service A
service C
service D
repository A
repository C
リポジトリ
repository D
103
外部API接続用のRepositoryが残ってる
104
RepositoryクラスのBean化(API接続用)
@Configurationpublic class SampleConfiguration { @Bean public SampleApiClient SampleApiClient(
HttpClient httpClient,@Value("${sample.server.scheme}") String scheme,@Value("${sample.server.host}") String host,@Value("${sample.server.port}") Integer port
) {・・・
}
@Bean public SampleRepository SampleRepository (SampleApiClient sampleApiClient) {・・・}}
# application.yml
sample:scheme: httphost: sample.ameba.jpport: 80
105
RepositoryクラスのBean化(API接続用)@ConditionalOnProperty (
prefix = "sample", name = "enabled",havingValue = "true",matchIfMissing = false
)@Configurationpublic class SampleConfiguration { @Bean public SampleApiClient SampleApiClient(
HttpClient httpClient,@Value("${sample.server.scheme}") String scheme,@Value("${sample.server.host}") String host,@Value("${sample.server.port}") Integer port
) {・・・
}
@Bean public SampleRepository SampleRepository (SampleApiClient sampleApiClient) {・・・}}
# application.yml
sample:scheme: httphost: sample.ameba.jpport: 80enabled: true
106
まとめたことによってBean化されるクラスが増大
facadeservice B
repository B
facade
service A
service C
service D
repository A
repository C
リポジトリ
repository D
107
まとめたことによってBean化されるクラスが増大
facadeservice B
repository B
facade
service A
service C
service D
repository A
repository C
リポジトリ
repository D
108
共通化して起こった問題もSpringの機能で解決
109
保守性の低下
増大していく開発コスト
ビューとビジネスロジックの強い依存関係
刷新前システムの課題
110
CyberAgent, Inc. All Rights Reserved
ビューとビジネスロジックの 強い依存関係
111
Data Store
刷新前のシステム
アメブロ
ビジネスロジック
ORM
他サービスAPI
他サービスのAPI
112
ビュー
刷新前のチームとシステムの関わり方
113
サーバーサイドフロントエンド
刷新前のチームとシステムの関わり方
サーバーとフロントでチームが別れている
ただし同じシステムをさわる
フロントエンドの最新技術導入しづらい
アメブロ
ORM
ビジネスロジック
ビュー
114
刷新前の他部署との連携状況
115
刷新前の他部署との連携状況アメブロ
ビジネスロジック
ORM
秋葉原ラボ
ティーン向けブログサービス
etc..
記事データ取りたい
アメブロにも投稿したい
この中に組み込まれている機能を使いたい
116
ビュー
◯◯API
ORM
新規作成
ビジネスロジック
API Centricな
アーキテクチャ ・
117
Data Store
刷新前のシステム(再掲)
アメブロ
ビジネスロジック
ORM
他サービスAPI
他サービスのAPI
118
ビュー
刷新後のシステム
PC アメブロ
Controller
API
Facade
Service
スマホ アメブロ
Controller
Facade
Service
Controller
Facade
Service
Repository
ビューを生成する処理に集中
ビジネスロジックの提供に集中
Data Store
他サービスのAPI
119
JSON
ビュー ビュー
すぐに良いことが
120
他部署からの依頼対応工数の削減
AMPを特別対応なしで完了
スマホ版フロントエンドの刷新も特別対応なし
参考) アメブロ2016 ~ React/ReduxでつくるIsomorphic web app ~
https://developers.cyberagent.co.jp/blog/archives/636/
121
CyberAgent, Inc. All Rights Reserved
API Centricの課題
122
通信コスト
123
複数レイヤにキャッシュを入れて対応
PC アメブロ
Controller
API
Facade
Service
スマホ アメブロ
Controller
Facade
Service
Controller
Facade
Service
Repository
Data Store
他サービスのAPI
124
ビュー ビュー
Client でキャッシュ
Repositoryでキャッシュ
APIの呼び出し元・先が
わかりづらい
(静的な意味で)
125
未解決
126
CyberAgent, Inc. All Rights Reserved
まとめ
127
技術面
ビジネス面
組織面
128
技術面でよかったこと
129
フロントエンドの技術がトレンドに乗りやすくなった
参考) アメブロ2016 ~ React/ReduxでつくるIsomorphic web app ~
https://developers.cyberagent.co.jp/blog/archives/636/
一般的なアーキテクチャに則った作りにできた
セキュリティリスクも抑制できている
技術的によかったこと
130
ビジネス面でよかったこと
131
AMP対応を短期間で完遂
フロントエンドを刷新しPVや広告収益が
短期間でより多くの施策が実現可能に
ビジネス的によかったこと
132
組織面でよかったこと
133
フロントエンドの開発チームを分離できた
新規の人材を取り込みやすくなった
よりよいサービスにしようという意識が強くなった
組織的によかったこと
134
CyberAgent, Inc. All Rights Reserved
最後に
135
本当に
やってよかった
136