Slide 1

Slide 1 text

Confidential All Rights Reserved.Copyright© 2022 RAKUS Co.,Ltd.
 サービス開発の理想と現実
 短納期でローンチした新サービスをJavaで開発した話
 2022/6/19 JJUG CCC 2022 Spring
 1


Slide 2

Slide 2 text

自己紹介
 三田 英一
 株式会社ラクス 開発統括部
 <経歴>
 SIにてシステム運用・開発を経験した後、
 2014年に株式会社ラクスに入社
 ずっと1つのサービスの機能開発や運用を担当
 @eichisanden
 2


Slide 3

Slide 3 text

● はじめに
 ● 開発体制
 ● Javaのバージョン
 ● フレームワーク
 ● APIクライアント
 ● マルチテナント構成
 ● セキュリティ
 ● タイムゾーン対応
 ● フロントエンド
 ● クラス設計
 ● ログ
 ● おわりに
 3
 目次


Slide 4

Slide 4 text

● はじめに
 ● 開発体制
 ● Javaのバージョン
 ● フレームワーク
 ● APIクライアント
 ● マルチテナント構成
 ● セキュリティ
 ● タイムゾーン対応
 ● フロントエンド
 ● クラス設計
 ● ログ
 ● おわりに
 4


Slide 5

Slide 5 text

はじめに
 2022年1月に電子帳簿保存法が改正されることになり、それに合せて急遽新サー ビスの開発プロジェクトが立ち上がりました。
 この発表は6ヶ月の短期間でJavaで新サービスを開発をした事例の紹介になりま す。
 
 法律が絡み競合他社も一斉に同じようなサービスをリリースしてくるので納期は 伸ばせない... ただ自社サービスとして長い間保守開発が続くので技術的な負債 を最初から作りたくない...
 この様な葛藤の中でどのように開発を進めたのかお話したいと思います。
 5


Slide 6

Slide 6 text

今回開発したサービス「楽楽電子保存」について
 6


Slide 7

Slide 7 text

● はじめに
 ● 開発体制
 ● Javaのバージョン
 ● フレームワーク
 ● APIクライアント
 ● マルチテナント構成
 ● セキュリティ
 ● タイムゾーン対応
 ● フロントエンド
 ● クラス設計
 ● ログ
 ● おわりに
 7


Slide 8

Slide 8 text

開発体制
 既存サービスを開発している2チームが開発
 8
 アルファ・チーム 6名 ブラボー・チーム 7名 Tech Lead(me)
 PM
 PL/PdM
 基盤部分を実装するの 1人じゃ無理!! ※実際のチーム名とは異なります


Slide 9

Slide 9 text

基盤チーム 5名 開発体制
 一時的に基盤チームを立ち上げ
 9
 アルファ・チーム 4名 ブラボー・チーム 5名 Tech Lead(me)
 PM
 PL/PdM
 開発チームからメンバーを借りて 
 WG的なチームで基盤部分を一気に作り上げた 
 大凡タスクが完了したら開発チームに人を戻した 


Slide 10

Slide 10 text

開発体制
 「チームトポロジー」という本にこのようなチーム分けの話がありました。
 (最近出た本なので読んだのは開発後ですが)
 10
 ❖ ストリームアラインドチーム
 ➢ 顧客に直接価値を届けるいわゆる開発チーム ❖ プラットフォームチーム
 ➢ インフラやツール、共通サービスなどを提供するチーム ❖ イネイブリングチーム
 ➢ 他のチームが技術やスキルを身につけるのを支援するチーム ❖ コンプリケイテッド・サブシステムチーム
 ➢ 複雑なサブシステムやコンポーネントを扱う専門チーム ● 図らずも今回の基盤チームはプラットフォームチームとイネイブリン グチーム的な役割を担っていた
 ● 現在、組織が拡大してチーム数が増えていっている状況の中、どう やって新しい技術を広めていくか悩んでいるところだったので非常に 参考になりました
 チームトポロジー
 価値あるソフトウェアをすばやく届ける適応型組織設計
 マシュー・スケルトン 著 マニュエル・パイス 著


Slide 11

Slide 11 text

● はじめに
 ● 開発体制
 ● Javaのバージョン
 ● フレームワーク
 ● APIクライアント
 ● マルチテナント構成
 ● セキュリティ
 ● タイムゾーン対応
 ● フロントエンド
 ● クラス設計
 ● ログ
 ● おわりに
 11


Slide 12

Slide 12 text

Javaのバージョン
 ● 既存サービスではJava11を使っている
 ● 取り込めるどうか微妙なタイミングにJava17 LTSがリリースされた
  →Java11 or 17 さてどうする?
 
 12


Slide 13

Slide 13 text

Javaのバージョン
 ● サポート期間の観点ではどうか?
 ○ Java11でもJava17でも十分長いサポート期間を提供してくれているベンダー がいるので決定的な判断ポイントにはならなかった 
 例)Amazon Correto
 13
 https://aws.amazon.com/jp/corretto/faqs/#support より引用

Slide 14

Slide 14 text

Javaのバージョン
 ● ツール類やMWは対応しているか?
 ○ リリースサイクルが半年になった恩恵か?全然問題なかった ○ IDEは商用のIntelliJ IDEAだったからかも(他は分からない) ○ JacksonがRecordに対応していないのでは?と思ったが全然そんなことな かった 14


Slide 15

Slide 15 text

Javaのバージョン
 ● 言語仕様の差分は激しくないか?
 ○ 戸惑うような大きな言語仕様の差分はなかった ○ Java8から一気にジャンプアップだと教育が大変だったかも ○ 使いたい機能追加は結構あった 15


Slide 16

Slide 16 text

Javaのバージョン
 結論:Java17を採用した☺
 
 16
 ● いつか17に上げるので移行コストが掛からない方が良い
 ● 新しい言語機能が使えるのはDevEx的にGood
 ● 学習コストはJava11と17の差分を説明したぐらいで軽微


Slide 17

Slide 17 text

Javaのバージョン
 時間がなくて出来なかったこと😓
 
 17
 ● 使ったことがないGCの検証
 ○ 採用したかはともかく調査ぐらいはしたかった ○ システム特性的に問題なさそうなことだけ確認してG1GCを選択 ● GraalVMを検証したかった
 ○ フルネイティブ化出来なくても速くなるという話を聞いたので

Slide 18

Slide 18 text

Javaのバージョン
 ⚠ 顧客向けクラウドサービスでは
 Oracle No-Fee Terms and Conditions (NFTC)
 の対象外なのでご注意ください
 
 ライセンス事項を確認することをお勧めします
 18


Slide 19

Slide 19 text

Javaのバージョン
 17の良かった新機能☺ ※Java11との比較です
 ● Record
 19
 /** * recordで書いた場合 */ public record LoginId(long value) { public LoginId { if (value <= 0L) { throw new IllegalArgumentException(); } } } Immutableなクラスが短く書けて便利なのでValue Objectの実装で多用した。
 
 /** * classで書いた場合(今までの書き方) */ public class LoginId { private final long value; public LoginId(long value) { if (value <= 0L) { throw new IllegalArgumentException(); } this.value = value; } public long getValue() { return this.value; } }

Slide 20

Slide 20 text

Javaのバージョン
 もしかしてrecordがあればlombokは不要では?🤔
 というチームメンバーから疑問の声が
 →そんなことはなかった
  確かに@Valueは使わないかもしれないが他のメソッドは使いたい
 20
 アノテーション 用途 @Slf4j Logger log = org.slf4j.LoggerFactory.getLogger(...);のショートハンド。 めちゃくちゃ使う @AllArgsConstructorなどコンストラクター系 コンストラクタでDIをインジェクションしたいのでめちゃくちゃ使う @NonNull 引数の必須チェックなどに使用する

Slide 21

Slide 21 text

Javaのバージョン
 Java17の良かった新機能
 21
 ● テキストブロック
 ○ 個人的には長年待ち望んでいた機能 ○ SQLを文字列に書いたりする機会が減って思ったよりは使用ケースは少なかった ○ テストコードではかなり使用した mockMvc.perform(MockMvcRequestBuilders.put("/user") .content("""{ "id": 1, "name": "Taro Yamada", "age": 23 }""").contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(content().json(expected, true))

Slide 22

Slide 22 text

Javaのバージョン
 Java17の良かった機能
 ● instanceofのパターンマッチング
 22
 そんなに多用するものではないが地味に便利
 private ContentCachingResponseWrapper wrapResponse(HttpServletResponse response) { if (response instanceof ContentCachingResponseWrapper wrapper) { return wrapper; } else { return new ContentCachingResponseWrapper(response); } }

Slide 23

Slide 23 text

Javaのバージョン
 Java17の地味だけど良かった機能
 ● stream.toList
 23
 List list = List.of("", "a"); List list2 = list.stream().filter(a -> !a.isEmpty()).toList(); collect(Collectors.toList())は長いし直感的じゃないのですごく便利
 ● NullPointerExceptionのエラーメッセージ改善
 String s = getS(); getNull(s.toUpperCase()).getBytes(); Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.toUpperCase()" because "s" is null at BasicCompletionDemo.main(Learning.java:7) 数珠つなぎに書いてたりすると、どこがnullなのか分からないことがあったが解消されている


Slide 24

Slide 24 text

● はじめに
 ● 開発体制
 ● Javaのバージョン
 ● フレームワーク
 ● APIクライアント
 ● マルチテナント構成
 ● セキュリティ
 ● タイムゾーン対応
 ● フロントエンド
 ● クラス設計
 ● ログ
 ● おわりに
 24


Slide 25

Slide 25 text

フレームワーク
 結論:Spring Bootを選択した
 ○ 現状、特殊な事情がない限りSpring Bootで良さそう ○ 既存サービスではSpring MVCなのでbootJarがとても楽に感じた ■ Tomcatの面倒をインフラにお願いしないで済むのもとても良い ○ ヘルスチェックを実装しなくて良いのでActuatorが便利だった。 
 25


Slide 26

Slide 26 text

フレームワーク
 ● Javaの性能監視にはJolokiaを使用
 ○ jmxのエンドポイントの設定が不要 ○ APIでJSONを取得するスタイルなので汎用性が高い ● Spring Actuatorが対応しているので、機能を有効にしてjolokia-coreを依存関 係に追加するだけで使用可能で便利だった。
 
 
 
 ● デフォルトでは組み込みTomcatのMbeanが取得できないので下記設定を追 加しました
 26
 # Jolokiaでメトリクス取得するため、TomcatのMbeanを取得可能にする server.tomcat.mbeanregistry.enabled: true # healthとjolokiaを有効に management.endpoints.web.exposure.include: "health,jolokia"

Slide 27

Slide 27 text

● はじめに
 ● 開発体制
 ● Javaのバージョン
 ● フレームワーク
 ● APIクライアント
 ● マルチテナント構成
 ● セキュリティ
 ● タイムゾーン対応
 ● フロントエンド
 ● クラス設計
 ● ログ
 ● おわりに
 27


Slide 28

Slide 28 text

APIクライアント
 ● SpringのRestTemplateを使おうと思ったが、調べてみると今後はメンテナンスモードに なるということだったので比較検討しました
 ○ 真っ先に上がる候補はSpringのWebClientですが、このためだけにWebFluxを入 れたくないので不採用とした ○ 結局、OKHttp(Retrofit)を採用した 
 ● OkHttp, Retrofitとは
 ○ OkHttpはHTTP通信に特化した軽量なライブラリでAndroidでよく使われているみた いです。 ○ RetrofitはOkHttpをラップしてより便利に使えるようにしたライブラリです。 ○ 去年のアドベントカレンダーで3本記事を書きました 28
 Retrofit+OkHttpでAPIエラーの時のレスポンスの取得ではまった Retrofit+OkHttpでmultipart/form-dataなAPIの呼び出してハマった Retrofit+OkHttpでファイルダウンロードするAPIを呼び出す

Slide 29

Slide 29 text

APIクライアント
 29
 interface UsersApiService { @GET("{tenantId}/users" ) Call userList(@Path("tenantId") int tenantId, @Query("sort") String sort); } // Responseを詰めるクラスも作っておきます record ResponseData(String status, Integer code, List userList){}; record User(String name, String mailAddress){}; interfaceにAPIごとにメソッドを宣言します。 
 上記の例はGETで {tenantId}/users?sort={sort} を呼び出してレスポンスボディをResponseData型に詰めるという定義です 
 Retrofit retorofit = new Retrofit.Builder() .baseUrl("http://localhost:8080" ) // JSONを変換するコンバーターを指定(今回は Jacksonを使用) .addConverterFactory(JacksonConverterFactory.create()) .build(); // 先ほど作成したinterfaceからインスタンスを生成 UsersApiService service = retorofit.create(UsersApiService. class); // API実行! ResponseData data = service.userList(1, "asc").execute().body(); あとは、下記のように呼び出します。UserApiServiceのメソッド呼び出しは普通のJavaのクラスを呼ぶような感覚で呼び 出すことができます。 ● Hello World


Slide 30

Slide 30 text

● はじめに
 ● 開発体制
 ● Javaのバージョン
 ● フレームワーク
 ● APIクライアント
 ● マルチテナント構成
 ● セキュリティ
 ● タイムゾーン対応
 ● フロントエンド
 ● クラス設計
 ● ログ
 ● おわりに
 30


Slide 31

Slide 31 text

マルチテナント構成
 マルチテナント構成でサービスを提供しています
 ■マルチテナント構成とは?
 サーバーを複数テナントで共用する構成
 ● コストメリット
 ● スケーラビリティ
 
 31
 https://jesusgilhernandez.com/2012/12/13/multitenancy-architecture/

Slide 32

Slide 32 text

 低                                                 マルチテナント構成
 高
 32
  高                                                 A. データベース分離方式 B. 単一データベース・個別スキーマ方式 C. 単一データベース・共通スキーマ方式 初期構築コスト
 運用コスト
 —----- -------- —----- -------- —----- -------- —----- -------- —----- -------- —----- -------- —----- -------- —----- -------- —----- -------- —----- -------- —----- -------- —----- -------- —----- -------- —----- -------- マルチテナント構成の実現方法
 低


Slide 33

Slide 33 text

マルチテナント構成
 単一データベース・共通スキーマ方式を採用
 ● これまでは単一データベース・個別スキーマ方式を採用していたがお客さんが増えるに従っ てスキーマ数が増えることで管理が大変という問題がある
 ● 今回はユーザ特性的にもこの方式が向いていたので無理なく採用
 33
 テナントID 請求書ID 金額 1 100 1,000,000 2 101 2,000,000 基本すべてのテーブルにテナントIDを持ちます 


Slide 34

Slide 34 text

マルチテナント構成
 34
 CREATE POLICY isolation_policy ON [table_name] USING (tenant_id = current_setting('app.current_tenant_id')::bigint); ● PostgreSQLのRow Level Security(RLS)で他テナントのレコードを間違って検索しないことを担 保
 ○ 万が一SQLでtenant_idでの絞り込みを間違っても、app.current_tenant_idに指定した以外 のデータは取得できないことが担保できる

Slide 35

Slide 35 text

マルチテナント構成
 35
 ● app.current_tenant_idの設定はトランザクションごとに設定するようにspringの DataSourceTransactionManagerを拡張
 public class MyTransactionManager extends DataSourceTransactionManager { @Override protected void prepareTransactionalConnection (Connection con, TransactionDefinition definition) throws SQLException { super.prepareTransactionalConnection(con, definition); try (Statement stmt = con.createStatement()) { stmt.execute("SET local app.current_tenant_id = " + TenantContextHolder .getTenantId()); } } } ● ここまで仕組みを作ったのでこれで安心できた
 ● RLSはSaaSの業界で最近流行っているようで沢山事例が公開されていて参考になりました


Slide 36

Slide 36 text

● はじめに
 ● 開発体制
 ● Javaのバージョン
 ● フレームワーク
 ● APIクライアント
 ● マルチテナント構成
 ● セキュリティ
 ● タイムゾーン対応
 ● フロントエンド
 ● クラス設計
 ● ログ
 ● おわりに
 36


Slide 37

Slide 37 text

セキュリティ
 ● 認証やCSRFチェックはSpring Securityを使用
 ○ デフォルトで必須級のセキュリティ系のヘッダーを付与してくれるので楽だった 37
 Cache-Control: no-store, no-cache, must-revalidate, max-age=0 Pragma: no-cache Expires: 0 X-XSS-Protection: 1; mode=block X-Frame-Options: DENY X-Content-Type-Options: nosniff Strict-Transport-Security: max-age=31536000; includeSubDomains

Slide 38

Slide 38 text

セキュリティ
 38
 不足するヘッダーだけ自分で設定した Content-Security-Policyはユーザが沢山いる状態で入れるのは大変なので早めに入れられて良かった Referrer-Policy "strict-origin-when-cross-origin" Content-Security-Policy "default-src 'self'; connect-src 'self' https://www.google-analytics.com; img-src 'self' https://www.google-analytics.com; script-src 'self' https://www.googletagmanager.com https://www.google-analytics.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com/; font-src https://fonts.googleapis.com/ https://fonts.gstatic.com/; child-src 'none'; object-src 'none'" 「体系的に学ぶ 安全なWebアプリケーションの作り方 第2版 脆弱性が生 まれる原理と対策の実践」(徳丸本)は必読ですが、 ちょっと前に出た「Webブラウザセキュリティ」という本もとても参考になり ました。 Webブラウザセキュリティ ― Webアプリケーションの安全性を支える仕組みを整理する 米内貴志 著


Slide 39

Slide 39 text

セキュリティ
 ● 認証処理を組み込んだところ性能問題が発生
 ○ 計測したらログインに2秒近く掛かっていた。 ○ プロファイルを取ってみるとBCryptPasswordEncoderが遅いことが分かった ○ 社内のセキュリティ要件に従うためstrengthに14(16384回ストレッチ)を指定したのが原因 ■ この件をQiitaにまとめて下さっている人がいました ● JavaでBCrypt + ストレッチ回数ごとの処理時間計測 ● デフォルトの1024回ストレッチでも100ms以上掛かっているのでそもそもコストが高い処理 ○ BCryptPasswordEncoderを使用せずに自前実装に切り替えたところ1500msから350msまで改善 しました ■ なんでこんなに違いが出るのかは細かいことは分かっていません。 39


Slide 40

Slide 40 text

● はじめに
 ● 開発体制
 ● Javaのバージョン
 ● フレームワーク
 ● APIクライアント
 ● マルチテナント構成
 ● セキュリティ
 ● タイムゾーン対応
 ● フロントエンド
 ● クラス設計
 ● ログ
 ● おわりに
 40


Slide 41

Slide 41 text

タイムゾーン対応
 ● ファーストリリースでは日本国内での使用のみ想定すれば良いのだが、将来 的に海外対応が必要になる確度が高いので最初からTimezoneありきで実装を 行った。
 ○ LocalDatetimeで実装して後から海外対応しようとすると修正箇所が多すぎて辛い ○ ただし、夏時間を考慮しようとすると途端に難しくなるのでここでは考慮しない 41
 APサーバ ユーザの入力値 :2022-05-10 10:00:00 2022-05-10 T01:00:00+00 ZonedDateTime: 2022-05-10 10:00:00(Asia/Tokyo) OffsetDateTime: 2022-05-10 01:00:00+00 DB
 とりあえずAsia/Tokyo固定 
 でZonedDateTimeで受け取る 
 OffsetDateTime(UTC)に変換して 処理する
 DBにはUTCで格納 
 ※データの取得は逆の順番で同じように行う 


Slide 42

Slide 42 text

タイムゾーン対応
 42
 ● PostgreSQLの timestamp with time zone型について
 timestamp with time zoneについて内部に格納されている値は常に UTCです(協定世界時、歴史的にグリニッジ標準時 GMTとして知られています)。 時間帯が明示的に指定された入力値は、その時間帯に適したオフセットを使用して UTCに変 換されます。 入力文字列に時間帯が指定されていない場合は、システムの TimeZoneパラメータに示されている値が時間 帯とみなされ、timezone時間帯用のオフセットを使用して UTCに変換されます。 (公式ドキュメントより引用) 初めて使いましたが、下記の点がちょっと意外でした
 ● 時間帯を明示しないと実行環境のTimezoneへの変換が自動で行われる
 ● 名前に反してtimezone情報は保持しない。常にUTCに変換して保存される
 ○ 確かにtimezoneあり/なし、どちらも格納サイズは8バイトで違いはない

Slide 43

Slide 43 text

タイムゾーン対応
 43
 app=# set timezone to 'Asia/Tokyo'; タイムゾーンをAsia/Tokyoにセットする(大抵の場合デフォルトこれ) SET app=# insert into test values(2, '2022-05-09 16:47:29'); 日本時間(+0900)の2022-05-09 16:47:29としてインサート INSERT 0 1 app=# select * from test; id | start_time ----+------------------------ 2 | 2022-05-09 16:47:29+09 日本時間(+0900)で取得される。 (1 row) ● SQLクラインアントから実行すると、自動的に日本時間に変換してくれるの でデバッグや運用でのDB操作は問題なさそう


Slide 44

Slide 44 text

タイムゾーン対応
 ○ JavaからDBアクセス時に実行環境のtimezoneの影響を受けないようにJVMパラメータで UTC固定にした 44
 -Duser.timezone=UTC ○ 開発当初はuser.timezoneを指定せず、更にDB Unitのテストデータにもオフセットを指定して いなかったため日本時間としてロードされてしまい日本時間なのかUTCなのか訳が分から ない状態になり現場を混乱させてしまった ○ オフショア開発する可能性もあったのでテストデータにはオフセットを明記するルールにして おいた方が良かったと後から思いました

Slide 45

Slide 45 text

● はじめに
 ● 開発体制
 ● Javaのバージョン
 ● フレームワーク
 ● APIクライアント
 ● マルチテナント構成
 ● セキュリティ
 ● タイムゾーン対応
 ● フロントエンド
 ● クラス設計
 ● ログ
 ● おわりに
 45


Slide 46

Slide 46 text

フロントエンド
 ● 既存サービスを踏襲して、今回のサービスでもReactを採用
 ○ サーバー側はJSONを返却するAPIを実装する ○ I/Fだけ固めてしまってフロント側は基本的にお任せ ○ サーバ側だけでも覚えることが多くなってきており責任分界点ができて良い 
 ● フロントエンドについてはJJUG CCC 2021 Springで同僚が話をしていますの で良ければご覧ください
 ○ 「フロントエンド・バックエンド分離の道のり」 ○ https://fortee.jp/jjug-ccc-2021-spring/proposal/a0cc4346-f001-44dc-8df4-5974ce40b767 ○ https://www.youtube.com/watch?v=1b3riDczwkQ 
 46


Slide 47

Slide 47 text

● はじめに
 ● 開発体制
 ● Javaのバージョン
 ● フレームワーク
 ● APIクライアント
 ● マルチテナント構成
 ● セキュリティ
 ● タイムゾーン対応
 ● フロントエンド
 ● クラス設計
 ● ログ
 ● おわりに
 47


Slide 48

Slide 48 text

クラス設計
 Springのstereotypeを使って素直に実装すると3層アーキテクチャになる
 
 48
 @Controller @Service @Repository ビュー: JSPなど プレゼンテーション層(UI層):
  表示、入力内容受け取り、入力内容のチェック、
  UIに合わせた形式変換
 ビジネスロジック層(ドメイン層):
  業務処理を実装する
 データアクセス層(インフラ層):
  データベースとやりとりを実装する


Slide 49

Slide 49 text

クラス設計
 ● 3層アーキテクチャのメリット
 ○ いわゆる「トランザクションスクリプト」に処理をつらつら書いて行けばやりたいことが実現できる(オブ ジェクト指向を分かってなくていい) ○ クラス設計にコストが掛からないので高速に開発できる ○ ユースケース単位で分担して実装すれば良いので平行して作業しやすい 
 ● 3層アーキテクチャのデメリット
 ○ 処理の共通化はその人任せになり同じ処理が重複して書かれがちになる ○ 修正の影響がどこに出るのか探しにくい ○ 影響があちこちにでてしまう 
 デメリットは既存サービスで味わってきた。
 長期メンテナンスには辛い構造なので時間がなくても今回のサービスでは採用しない
 49


Slide 50

Slide 50 text

クラス設計
 デメリットはビジネスロジック層に集中しているのでテコ入れする
 50
 @Controller @Service @Repository ビュー: JSPなど プレゼンテーション層(UI層):
  表示、入力内容受け取り、入力内容のチェック、
  UIに合わせた形式変換
 ビジネスロジック層(ドメイン層):
  業務処理を実装する
 データアクセス層(インフラ層):
  データベースとやりとりを実装する
 この層を細分化す るルールを作成す れば良さそう🤔

Slide 51

Slide 51 text

クラス設計
 オニオンアーキテクチャを採用した
 51
 アプリケーション層 ドメイン層 インフラストラクチャ層 ユーザインターフェース層 アプリケーション層 ドメイン層 インフラストラクチャ層 ユーザインターフェース層 レイヤードアーキテクチャ
 オニオンアーキテクチャ
 3層アーキ テクチャのビ ジネスロジッ ク層が分離 された
 ドメイン層
 アプリケーション層 
 ユーザインターフェース層 
 インフラストラクチャ層 
 丸で表現するとタマネギみたい な形になるのが由来
 この依存度 の向きが逆
 他にもヘキサゴナルアーキテクチャやクリーンアーキテクチャがあるがそこまで複雑なものは必要なくオニオンアーキテクチャがちょうど 良かった


Slide 52

Slide 52 text

クラス設計
 ● ユースケースをアプリケーション層に、業務ロジックをドメイン層に書くように分 離できた
 ○ ドメイン層をエンティティやバリューオブジェクトなど、共通認識を持って実装できるのが 大きい ○ とりあえず小さく始めるならバリューオブジェクトに切り出すことをお勧め 52


Slide 53

Slide 53 text

クラス設計
 53
 ● とは言え、クラスを設計すること自体に慣れてなく苦戦は必至だった
 ○ だいぶ前から読書会や勉強会を主催、良記事の紹介など地道な啓蒙活動を行った ○ 既存サービスで部分的に実践 ○ 理解を深めるため何冊も本を読みました エリック・エヴァンスのドメイン駆動設計 (IT Architects’Archive ソフトウェア開発の実践) エリック・エヴァンス著
 ドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本 成瀬 允宣 著
 ドメイン駆動設計 モデリング/実装ガイド 松岡幸一郎 著
 ドメイン駆動設計 サンプルコード&FAQ 松岡幸一郎 著
 セキュア・バイ・デザイン Dan Bergh Johnsson, Daniel Deogun, Daniel Sawano 著


Slide 54

Slide 54 text

● はじめに
 ● 開発体制
 ● Javaのバージョン
 ● フレームワーク
 ● APIクライアント
 ● マルチテナント構成
 ● セキュリティ
 ● タイムゾーン対応
 ● フロントエンド
 ● クラス設計
 ● ログ
 ● おわりに
 54


Slide 55

Slide 55 text

ログ
 ● 運用時の調査のためリクエスト/レスポンス情報をログ出力した い
 ● ユーザの入力値など機微情報は除外してデータを特定するID項 目だけをログに出力したい
 ● Springのフィルターは全てログに出力してしまうため使えない
 ● 機微情報を意識しながらControllerでログ出力実装をしたくない
 →ログ出力をするFilterを実装すれば簡単にできそう🤔
 55


Slide 56

Slide 56 text

ログ
 ● Responseからデータを取得しログ出力できたが後続の処理でス トリームが閉じられていると言ってエラーになってしまう
 56
 やってみたら意外と難しかった。


Slide 57

Slide 57 text

ログ
 ● AbstractRequestLoggingFilterを参考にContentCachingRequestWrapper、 ContentCachingResponseWrapperを使用するように実装しました
 57
 @AllArgsConstructor public class RequestAndResponseLoggingFilter extends OncePerRequestFilter { @Override protected void doFilterInternal (@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain) { doFilterWrapped(wrapRequest(request), wrapResponse(response), filterChain); } private void doFilterWrapped (ContentCachingResponseWrapper request, ContentCachingResponseWrapper response) { try { // リクエストログ出力 (予め指定してキーに絞ってログ出力) writeRequestLog(request); // 処理の実行 long start = System.currentTimeMillis(); filterChain.doFilter(request, response); long latency = System.currentTimeMillis() - start; // レスポンスログ出力 (予め指定してキーに絞ってログ出力) writeResponseLog(response , latency); } finally { response.copyBodyToResponse(); // レスポンスに書き込み } } private ContentCachingResponseWrapper wrapRequest(HttpServletRequest request) { return new ContentCachingResponseWrapper (request); } private ContentCachingResponseWrapper wrapResponse(HttpServletResponse response) { if (response instanceof ContentCachingResponseWrapper wrapper) { return wrapper; } else { return new ContentCachingResponseWrapper(response); } } }  ※メモリ使用量は増えると思いますので、ご注意を。


Slide 58

Slide 58 text

他にも沢山のタスクをこなす必要がある...
 ● パブリッククラウド or オンプレ検討
 ● コンテナ or VM or 物理サーバ
 ● システム構成検討
 ● スケールアウト方式検討(APP/DB)
 ● バックアップ・リストア検討
 ● ドメイン取得
 ● URL設計
 ● SSL証明書取得(暗号化方式, TLSバージョン)
 ● ログ設計(syslogの設計)
 ● CI設定
 ● systemd設定
 ● サーバのディレクトリ構成設計
 ● オブジェクトストレージ設計
 ● 各種タイムアウト値設計
 ● リクエストサイズの上限設定 (servlet.multipart.maxFileSize, servlet.multipart.maxRequestSize)
 ● Cookie設計
 ● セッション設計
 ● LBの設定(Sticky Session)
 ● PostgreSQLの設定値検討
 ● PostgreSQLのスキーマ設計
 ● PostgreSQLのRole設計
 ● コネクションプーリング検討
 58
 ● JVMパラメータ設計(ヒープサイズ、Metaspaceなど)
 ● GC設計(GC種類の検討、GCログなど)
 ● 性能測定
 ● パッケージ設計
 ● 使用するMWのライセンス確認
 ● 排他制御の方式検討
 ● ウィルススキャン検討
 ● コーディング・DB規約作成
 ● バッチ設計
 ● 制御文字チェック(nullやnbspなどバイト列)
 ● メール設計(spf、dkim、smtp、メールヘッダーの検討など)
 ● メールテンプレート設計(テンプレートエンジンは何を使うかなど)
 ● TomcatやSpringのエラー画面を表示させない対応
 ● ステージング設計
 ● ヘルスチェック設計
 ● APM検討
 ● 文字コード検討
 ● クローラー対策
 ● 脆弱性チェック
 ● 運用設計(スレッドダンプの取得方法の検討など)
 ● メッセージ設計(他言語対応しやすいようにベタ書き禁止)
 ● ORマッパーの検討
 ● Reactのためのmod_rewrite設定


Slide 59

Slide 59 text

🎊予定通り無事ローンチできた🎉
 
 
 (電子帳簿保存法に二年間の猶予期間が設けられたのはまた別の話😂) 
 59


Slide 60

Slide 60 text

おわりに
 ● すべてが理想通りとは行かなかったが納期を守ってリリースできた
 ● 久しぶりに新サービスを開発をしてみて、1つのサービスを作るために必 要なことは沢山あることを改めて実感した
 ● 新しい技術要素を試すためには日頃からの仕込みが重要
 ● 半年サイクルのリリースになりJavaはリリース直後でも安心して利用でき た
 60


Slide 61

Slide 61 text

🏁ご清聴ありがとうございました🏁
 61