Upgrade to Pro — share decks privately, control downloads, hide ads and more …

短納期でローンチした新サービスをJavaで開発した話/launched new service using Java

A1
June 17, 2022

短納期でローンチした新サービスをJavaで開発した話/launched new service using Java

JJUG CCC 2022 Spring 発表資料

https://fortee.jp/jjug-ccc-2022-spring/proposal/3bf78003-4672-457c-a8fc-47adb4a1812e

概要 / Abstract:
電子帳簿保存法の改正により急遽立ち上がった新規サービス開発。
久々の新規開発でテックリードの腕の見せ所とはりきる私。
新しい技術要素をモリモリ使って開発したい..ただ法改正がトリガーなので絶対に納期が延ばせないが考えなければいけないことは盛り沢山
・使用するJavaのバージョンとフレームワーク
・マルチテナントDB方式
・APIクライアント
・セキュリティ関連
・多言語、タイムゾーン対応
・フロントエンド
・クラス設計の方針
などなど
そのような状況の中でJavaを中心とした技術選定で妥協しなかったことや開発で苦労したことなど、開発事例をお話させていただきます。

A1

June 17, 2022
Tweet

More Decks by A1

Other Decks in Programming

Transcript

  1. Confidential All Rights Reserved.Copyright© 2022 RAKUS Co.,Ltd.

    サービス開発の理想と現実

    短納期でローンチした新サービスをJavaで開発した話

    2022/6/19 JJUG CCC 2022 Spring

    1


    View Slide

  2. 自己紹介

    三田 英一

    株式会社ラクス 開発統括部

    <経歴>

    SIにてシステム運用・開発を経験した後、

    2014年に株式会社ラクスに入社

    ずっと1つのサービスの機能開発や運用を担当

    @eichisanden

    2


    View Slide


  3. はじめに


    開発体制


    Javaのバージョン


    フレームワーク


    APIクライアント


    マルチテナント構成


    セキュリティ


    タイムゾーン対応


    フロントエンド


    クラス設計


    ログ


    おわりに

    3

    目次


    View Slide


  4. はじめに


    開発体制


    Javaのバージョン


    フレームワーク


    APIクライアント


    マルチテナント構成


    セキュリティ


    タイムゾーン対応


    フロントエンド


    クラス設計


    ログ


    おわりに

    4


    View Slide

  5. はじめに

    2022年1月に電子帳簿保存法が改正されることになり、それに合せて急遽新サー
    ビスの開発プロジェクトが立ち上がりました。

    この発表は6ヶ月の短期間でJavaで新サービスを開発をした事例の紹介になりま
    す。


    法律が絡み競合他社も一斉に同じようなサービスをリリースしてくるので納期は
    伸ばせない... ただ自社サービスとして長い間保守開発が続くので技術的な負債
    を最初から作りたくない...

    この様な葛藤の中でどのように開発を進めたのかお話したいと思います。

    5


    View Slide

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

    6


    View Slide


  7. はじめに


    開発体制


    Javaのバージョン


    フレームワーク


    APIクライアント


    マルチテナント構成


    セキュリティ


    タイムゾーン対応


    フロントエンド


    クラス設計


    ログ


    おわりに

    7


    View Slide

  8. 開発体制

    既存サービスを開発している2チームが開発

    8

    アルファ・チーム
    6名
    ブラボー・チーム
    7名
    Tech Lead(me)

    PM

    PL/PdM

    基盤部分を実装するの 1人じゃ無理!!
    ※実際のチーム名とは異なります


    View Slide

  9. 基盤チーム
    5名
    開発体制

    一時的に基盤チームを立ち上げ

    9

    アルファ・チーム
    4名
    ブラボー・チーム
    5名
    Tech Lead(me)

    PM

    PL/PdM

    開発チームからメンバーを借りて

    WG的なチームで基盤部分を一気に作り上げた

    大凡タスクが完了したら開発チームに人を戻した

    View Slide

  10. 開発体制

    「チームトポロジー」という本にこのようなチーム分けの話がありました。

    (最近出た本なので読んだのは開発後ですが)

    10

    ❖ ストリームアラインドチーム

    ➢ 顧客に直接価値を届けるいわゆる開発チーム
    ❖ プラットフォームチーム

    ➢ インフラやツール、共通サービスなどを提供するチーム
    ❖ イネイブリングチーム

    ➢ 他のチームが技術やスキルを身につけるのを支援するチーム
    ❖ コンプリケイテッド・サブシステムチーム

    ➢ 複雑なサブシステムやコンポーネントを扱う専門チーム
    ● 図らずも今回の基盤チームはプラットフォームチームとイネイブリン
    グチーム的な役割を担っていた

    ● 現在、組織が拡大してチーム数が増えていっている状況の中、どう
    やって新しい技術を広めていくか悩んでいるところだったので非常に
    参考になりました

    チームトポロジー

    価値あるソフトウェアをすばやく届ける適応型組織設計

    マシュー・スケルトン 著 マニュエル・パイス 著


    View Slide


  11. はじめに


    開発体制


    Javaのバージョン


    フレームワーク


    APIクライアント


    マルチテナント構成


    セキュリティ


    タイムゾーン対応


    フロントエンド


    クラス設計


    ログ


    おわりに

    11


    View Slide

  12. Javaのバージョン


    既存サービスではJava11を使っている


    取り込めるどうか微妙なタイミングにJava17 LTSがリリースされた

     →Java11 or 17 さてどうする?


    12


    View Slide

  13. Javaのバージョン


    サポート期間の観点ではどうか?

    ○ Java11でもJava17でも十分長いサポート期間を提供してくれているベンダー
    がいるので決定的な判断ポイントにはならなかった

    例)Amazon Correto

    13

    https://aws.amazon.com/jp/corretto/faqs/#support より引用

    View Slide

  14. Javaのバージョン


    ツール類やMWは対応しているか?

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


    View Slide

  15. Javaのバージョン

    ● 言語仕様の差分は激しくないか?

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


    View Slide

  16. Javaのバージョン

    結論:Java17を採用した☺


    16

    ● いつか17に上げるので移行コストが掛からない方が良い

    ● 新しい言語機能が使えるのはDevEx的にGood

    ● 学習コストはJava11と17の差分を説明したぐらいで軽微


    View Slide

  17. Javaのバージョン

    時間がなくて出来なかったこと😓


    17

    ● 使ったことがないGCの検証

    ○ 採用したかはともかく調査ぐらいはしたかった
    ○ システム特性的に問題なさそうなことだけ確認してG1GCを選択
    ● GraalVMを検証したかった

    ○ フルネイティブ化出来なくても速くなるという話を聞いたので

    View Slide

  18. Javaのバージョン

    ⚠ 顧客向けクラウドサービスでは

    Oracle No-Fee Terms and Conditions (NFTC)

    の対象外なのでご注意ください


    ライセンス事項を確認することをお勧めします

    18


    View Slide

  19. 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;
    }
    }

    View Slide

  20. Javaのバージョン

    もしかしてrecordがあればlombokは不要では?🤔

    というチームメンバーから疑問の声が

    →そんなことはなかった

     確かに@Valueは使わないかもしれないが他のメソッドは使いたい

    20

    アノテーション 用途
    @Slf4j Logger log = org.slf4j.LoggerFactory.getLogger(...);のショートハンド。
    めちゃくちゃ使う
    @AllArgsConstructorなどコンストラクター系 コンストラクタでDIをインジェクションしたいのでめちゃくちゃ使う
    @NonNull 引数の必須チェックなどに使用する

    View Slide

  21. 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))

    View Slide

  22. Javaのバージョン

    Java17の良かった機能


    instanceofのパターンマッチング

    22

    そんなに多用するものではないが地味に便利

    private ContentCachingResponseWrapper wrapResponse(HttpServletResponse response) {
    if (response instanceof ContentCachingResponseWrapper wrapper) {
    return wrapper;
    } else {
    return new ContentCachingResponseWrapper(response);
    }
    }

    View Slide

  23. 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なのか分からないことがあったが解消されている


    View Slide


  24. はじめに


    開発体制


    Javaのバージョン


    フレームワーク


    APIクライアント


    マルチテナント構成


    セキュリティ


    タイムゾーン対応


    フロントエンド


    クラス設計


    ログ


    おわりに

    24


    View Slide

  25. フレームワーク

    結論:Spring Bootを選択した

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

    25


    View Slide

  26. フレームワーク

    ● 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"

    View Slide


  27. はじめに


    開発体制


    Javaのバージョン


    フレームワーク


    APIクライアント


    マルチテナント構成


    セキュリティ


    タイムゾーン対応


    フロントエンド


    クラス設計


    ログ


    おわりに

    27


    View Slide

  28. 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を呼び出す

    View Slide

  29. 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


    View Slide


  30. はじめに


    開発体制


    Javaのバージョン


    フレームワーク


    APIクライアント


    マルチテナント構成


    セキュリティ


    タイムゾーン対応


    フロントエンド


    クラス設計


    ログ


    おわりに

    30


    View Slide

  31. マルチテナント構成

    マルチテナント構成でサービスを提供しています

    ■マルチテナント構成とは?

    サーバーを複数テナントで共用する構成


    コストメリット


    スケーラビリティ


    31

    https://jesusgilhernandez.com/2012/12/13/multitenancy-architecture/

    View Slide

  32.  低                                                
    マルチテナント構成

    高

    32

     高                                                
    A. データベース分離方式 B. 単一データベース・個別スキーマ方式 C. 単一データベース・共通スキーマ方式
    初期構築コスト

    運用コスト

    —-----
    --------
    —-----
    --------
    —-----
    --------
    —-----
    --------
    —-----
    --------
    —-----
    --------
    —-----
    --------
    —-----
    --------
    —-----
    --------
    —-----
    --------
    —-----
    --------
    —-----
    --------
    —-----
    --------
    —-----
    --------
    マルチテナント構成の実現方法

    低


    View Slide

  33. マルチテナント構成

    単一データベース・共通スキーマ方式を採用

    ● これまでは単一データベース・個別スキーマ方式を採用していたがお客さんが増えるに従っ
    てスキーマ数が増えることで管理が大変という問題がある

    ● 今回はユーザ特性的にもこの方式が向いていたので無理なく採用

    33

    テナントID 請求書ID 金額
    1 100 1,000,000
    2 101 2,000,000
    基本すべてのテーブルにテナントIDを持ちます

    View Slide

  34. マルチテナント構成

    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に指定した以外
    のデータは取得できないことが担保できる

    View Slide

  35. マルチテナント構成

    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の業界で最近流行っているようで沢山事例が公開されていて参考になりました


    View Slide


  36. はじめに


    開発体制


    Javaのバージョン


    フレームワーク


    APIクライアント


    マルチテナント構成


    セキュリティ


    タイムゾーン対応


    フロントエンド


    クラス設計


    ログ


    おわりに

    36


    View Slide

  37. セキュリティ


    認証や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

    View Slide

  38. セキュリティ

    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アプリケーションの安全性を支える仕組みを整理する 米内貴志 著


    View Slide

  39. セキュリティ


    認証処理を組み込んだところ性能問題が発生

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


    View Slide


  40. はじめに


    開発体制


    Javaのバージョン


    フレームワーク


    APIクライアント


    マルチテナント構成


    セキュリティ


    タイムゾーン対応


    フロントエンド


    クラス設計


    ログ


    おわりに

    40


    View Slide

  41. タイムゾーン対応


    ファーストリリースでは日本国内での使用のみ想定すれば良いのだが、将来
    的に海外対応が必要になる確度が高いので最初から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で格納 

    ※データの取得は逆の順番で同じように行う

    View Slide

  42. タイムゾーン対応

    42


    PostgreSQLの timestamp with time zone型について

    timestamp with time zoneについて内部に格納されている値は常に
    UTCです(協定世界時、歴史的にグリニッジ標準時
    GMTとして知られています)。 時間帯が明示的に指定された入力値は、その時間帯に適したオフセットを使用して
    UTCに変
    換されます。 入力文字列に時間帯が指定されていない場合は、システムの
    TimeZoneパラメータに示されている値が時間
    帯とみなされ、timezone時間帯用のオフセットを使用して
    UTCに変換されます。 (公式ドキュメントより引用)
    初めて使いましたが、下記の点がちょっと意外でした

    ● 時間帯を明示しないと実行環境のTimezoneへの変換が自動で行われる

    ● 名前に反してtimezone情報は保持しない。常にUTCに変換して保存される

    ○ 確かにtimezoneあり/なし、どちらも格納サイズは8バイトで違いはない

    View Slide

  43. タイムゾーン対応

    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操作は問題なさそう


    View Slide

  44. タイムゾーン対応

    ○ JavaからDBアクセス時に実行環境のtimezoneの影響を受けないようにJVMパラメータで
    UTC固定にした
    44

    -Duser.timezone=UTC
    ○ 開発当初はuser.timezoneを指定せず、更にDB Unitのテストデータにもオフセットを指定して
    いなかったため日本時間としてロードされてしまい日本時間なのかUTCなのか訳が分から
    ない状態になり現場を混乱させてしまった
    ○ オフショア開発する可能性もあったのでテストデータにはオフセットを明記するルールにして
    おいた方が良かったと後から思いました

    View Slide


  45. はじめに


    開発体制


    Javaのバージョン


    フレームワーク


    APIクライアント


    マルチテナント構成


    セキュリティ


    タイムゾーン対応


    フロントエンド


    クラス設計


    ログ


    おわりに

    45


    View Slide

  46. フロントエンド


    既存サービスを踏襲して、今回のサービスでも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


    View Slide


  47. はじめに


    開発体制


    Javaのバージョン


    フレームワーク


    APIクライアント


    マルチテナント構成


    セキュリティ


    タイムゾーン対応


    フロントエンド


    クラス設計


    ログ


    おわりに

    47


    View Slide

  48. クラス設計

    Springのstereotypeを使って素直に実装すると3層アーキテクチャになる


    48

    @Controller
    @Service
    @Repository
    ビュー: JSPなど
    プレゼンテーション層(UI層):

     表示、入力内容受け取り、入力内容のチェック、

     UIに合わせた形式変換

    ビジネスロジック層(ドメイン層):

     業務処理を実装する

    データアクセス層(インフラ層):

     データベースとやりとりを実装する


    View Slide

  49. クラス設計


    3層アーキテクチャのメリット

    ○ いわゆる「トランザクションスクリプト」に処理をつらつら書いて行けばやりたいことが実現できる(オブ
    ジェクト指向を分かってなくていい)
    ○ クラス設計にコストが掛からないので高速に開発できる
    ○ ユースケース単位で分担して実装すれば良いので平行して作業しやすい


    3層アーキテクチャのデメリット

    ○ 処理の共通化はその人任せになり同じ処理が重複して書かれがちになる
    ○ 修正の影響がどこに出るのか探しにくい
    ○ 影響があちこちにでてしまう

    デメリットは既存サービスで味わってきた。

    長期メンテナンスには辛い構造なので時間がなくても今回のサービスでは採用しない

    49


    View Slide

  50. クラス設計

    デメリットはビジネスロジック層に集中しているのでテコ入れする

    50

    @Controller
    @Service
    @Repository
    ビュー: JSPなど
    プレゼンテーション層(UI層):

     表示、入力内容受け取り、入力内容のチェック、

     UIに合わせた形式変換

    ビジネスロジック層(ドメイン層):

     業務処理を実装する

    データアクセス層(インフラ層):

     データベースとやりとりを実装する

    この層を細分化す
    るルールを作成す
    れば良さそう🤔

    View Slide

  51. クラス設計

    オニオンアーキテクチャを採用した

    51

    アプリケーション層
    ドメイン層
    インフラストラクチャ層
    ユーザインターフェース層
    アプリケーション層
    ドメイン層
    インフラストラクチャ層
    ユーザインターフェース層
    レイヤードアーキテクチャ
 オニオンアーキテクチャ

    3層アーキ
    テクチャのビ
    ジネスロジッ
    ク層が分離
    された

    ドメイン層

    アプリケーション層 

    ユーザインターフェース層 

    インフラストラクチャ層 

    丸で表現するとタマネギみたい
    な形になるのが由来

    この依存度
    の向きが逆

    他にもヘキサゴナルアーキテクチャやクリーンアーキテクチャがあるがそこまで複雑なものは必要なくオニオンアーキテクチャがちょうど
    良かった


    View Slide

  52. クラス設計


    ユースケースをアプリケーション層に、業務ロジックをドメイン層に書くように分
    離できた

    ○ ドメイン層をエンティティやバリューオブジェクトなど、共通認識を持って実装できるのが
    大きい
    ○ とりあえず小さく始めるならバリューオブジェクトに切り出すことをお勧め
    52


    View Slide

  53. クラス設計

    53

    ● とは言え、クラスを設計すること自体に慣れてなく苦戦は必至だった

    ○ だいぶ前から読書会や勉強会を主催、良記事の紹介など地道な啓蒙活動を行った
    ○ 既存サービスで部分的に実践
    ○ 理解を深めるため何冊も本を読みました
    エリック・エヴァンスのドメイン駆動設計 (IT Architects’Archive ソフトウェア開発の実践) エリック・エヴァンス著

    ドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本 成瀬 允宣 著

    ドメイン駆動設計 モデリング/実装ガイド 松岡幸一郎 著

    ドメイン駆動設計 サンプルコード&FAQ 松岡幸一郎 著

    セキュア・バイ・デザイン Dan Bergh Johnsson, Daniel Deogun, Daniel Sawano 著


    View Slide


  54. はじめに


    開発体制


    Javaのバージョン


    フレームワーク


    APIクライアント


    マルチテナント構成


    セキュリティ


    タイムゾーン対応


    フロントエンド


    クラス設計


    ログ


    おわりに

    54


    View Slide

  55. ログ


    運用時の調査のためリクエスト/レスポンス情報をログ出力した
    い


    ユーザの入力値など機微情報は除外してデータを特定するID項
    目だけをログに出力したい


    Springのフィルターは全てログに出力してしまうため使えない


    機微情報を意識しながらControllerでログ出力実装をしたくない

    →ログ出力をするFilterを実装すれば簡単にできそう🤔

    55


    View Slide

  56. ログ


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

    56

    やってみたら意外と難しかった。


    View Slide

  57. ログ


    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);
    }
    }
    } 
    ※メモリ使用量は増えると思いますので、ご注意を。


    View Slide

  58. 他にも沢山のタスクをこなす必要がある...

    ● パブリッククラウド 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設定


    View Slide

  59. 🎊予定通り無事ローンチできた🎉



    (電子帳簿保存法に二年間の猶予期間が設けられたのはまた別の話😂)

    59


    View Slide

  60. おわりに


    すべてが理想通りとは行かなかったが納期を守ってリリースできた


    久しぶりに新サービスを開発をしてみて、1つのサービスを作るために必
    要なことは沢山あることを改めて実感した


    新しい技術要素を試すためには日頃からの仕込みが重要


    半年サイクルのリリースになりJavaはリリース直後でも安心して利用でき
    た

    60


    View Slide

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

    61


    View Slide