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

replace of cart system on ZOZOTOWN

replace of cart system on ZOZOTOWN

TakahashiKazutaro

March 24, 2023
Tweet

More Decks by TakahashiKazutaro

Other Decks in Technology

Transcript

  1. © ZOZO, Inc. https://zozo.jp/
 • ファッションEC
 • 1,500以上のショップ、8,500以上のブランドの取り扱い
 • 常時90万点以上の商品アイテム数と毎日平均2,600点以上の新着

    商品 を掲載(2022年12月末時点)
 • ブランド古着のファッションゾーン「ZOZOUSED」や
 コスメ専門モール「ZOZOCOSME」、靴の専門モール
 「ZOZOSHOES」、ラグジュアリー&デザイナーズゾーン
 「ZOZOVILLA」を展開
 • 即日配送サービス
 • ギフトラッピングサービス
 • ツケ払い など
 3
  2. © ZOZO, Inc. 4 Agenda
 • カートリプレイスについて
 • カートリプレイスPhase1
 •

    カートリプレイスPhase2
 • 技術選定・ツール
 • 部内の取り組み
 • リプレイスの今後

  3. © ZOZO, Inc. カートリプレイスの目的
 成長し続けるZOZOTOWNを支え、お客様にいつでも快適に買い物をしていただけるサービスを提供 するため
 • セールなどの高負荷イベントに対応できること
 ◦ スケール可能であること


    ◦ キャパシティコントロールが可能であること
 • Datadog、SentryなどのSaaSを利用した運用監視の効率化ができること
 • CI/CDなどを取り入れ、開発生産性を向上できること
 • レガシー技術をモダン化できること
 7
  4. © ZOZO, Inc. 技術選定・開発ツールについて
 社内のOSS利用ガイドラインに基づいて実施
 • Javaのバージョン
 • Spring Bootのバージョン


    • ソフトウェアアーキテクチャ
 • ビルドツール
 • テストフレームワーク
 • AWSの使用サービス
 • ライブラリ
 ◦ O/R Mapper
 ◦ コードフォーマッター
 ◦ DBマイグレーションツール
 ◦ OpenAPI
 
 17 開発に使用しているツール
 • IntelliJ IDEA
 • GitHub
 ◦ GitHub Actions
 ▪ 単体テスト
 ▪ コードの自動フォーマット
 ▪ 必要に応じてDocker Imageの作成
 • Sentry
 • Datadog
 ◦ 監視
 ◦ 分析
 • SonarCloud
 ◦ 静的コード解析
 カート決済専任のSREがいるため、インフラ面で気軽に相談ができる

  5. © ZOZO, Inc. 検討中概要
 • APIからSQL Serverのストアドプロシージャの呼び出しを想定
 • Java +

    Spring Bootを使用して実現するためのライブラリの選定などを実施中
 18
  6. © ZOZO, Inc. 技術選定
 要件
 • APIからSQL Serverのストアドプロシージャを呼び出す
 • ストアドプロシージャは複数のResultSetを返

    す
 
 検証対象ライブラリ(O/R Mapper)
 • JPA
 • Spring Data JDBC
 • MyBatis
 • Doma 2
 
 比較内容
 • レイテンシ
 • コード量
 19
  7. © ZOZO, Inc. 技術選定(JPA)
 20 • 平均レイテンシ:45ms
 • コード量:
 •

    問題点:2つ目のResultSetを取得する ために2回目のDB接続が必要
 StoredProcedureQuery procedureQuery = createStoredProcedure( entityManager.createStoredProcedureQuery( "SampleProcedure", SampleProcedure.class)); setParameterForProcedure(procedureQuery, id); // 1回目の実行 if(procedureQuery.execute()) { // Outputパラメータ取得 Integer outputId = (Integer) procedureQuery.getOutputParameterValue("OutputId"); // 1つ目のResultSetの取得 List<FirstResult> firstResults = procedureQuery.getResultList(); // 2回目の実行準備 procedureQuery = createStoredProcedure( entityManager.createStoredProcedureQuery( "SampleProcedure", SampleProcedure.class)); setParameterForProcedure(procedureQuery, id); List<SecondResult> secondResults = new ArrayList<>(); if(procedureQuery.hasMoreResults()) { // 2つ目のResultSetの取得 secondResults = procedureQuery.getResultList(); }
  8. © ZOZO, Inc. 技術選定(Spring Data JDBC)
 21 • 平均レイテンシ:22ms
 •

    コード量:他のライブラリに比べると多 い
 CallableStatement callableStatement = connection.prepareCall("{call SampleProcedure(?,?)}"); callableStatement.setLong("id", id.value()); callableStatement.registerOutParameter("OutputId", Types.INTEGER); // 1つ目のResultSetの取得 ResultSet firstResultSet = callableStatement.executeQuery(); List<FirstResult> firstResults = new ArrayList<>(); while(firstResultSet.next()) { firstResults.add(new FirstResult( new Id(firstResultSet.getLong("Id")), new Name(firstResultSet.getString("Name")); } List<SecondResult> secondResults = new ArrayList<>(); if(callableStatement.getMoreResults()) { // 2つ目のResultSetの取得 ResultSet secondResultSet = callableStatement.getResultSet(); while(secondResultSet.next()) { secondResults.add(new SecondResult( new ItemId(secondResultSet.getLong("ItemId")), new ItemName(secondResultSet.getString("ItemName"))); } }
  9. © ZOZO, Inc. 技術選定(MyBatis)
 22 • 平均レイテンシ:149ms
 • コード量:Repository層としての量は少ないが、XMLにストアドプロシージャ呼び出し用の定義を書くため、全体とし てはあまり少なくならない


    List<Map<String, Object>> resultSet = procedureMapper.executeSampleProcedure(id.value(),name); // 1つ目のResultSetを取得 List<Object> firstResultObjects = (List<Object>) resultSet.get(0); List<FirstResult> firstResults = new ArrayList<>(); for (Object obj: firstResultObjects) { FirstResult firstResult = (FirstResult) obj; firstResults.add(new FirstResult( new Id(firstResult.getId())), new Name(firstResult.getName()); } // 2つ目のResultSetを取得 List<Object> secondResultObjects = (List<Object>) resultSet.get(1); List<SecondResult> secondResults = new ArrayList<>(); for (Object obj: secondResultObjects) { SecondResult secondResult = (SecondResult) obj; secondResults.add(new SecondResult( new ItemId(secondResult.getItemId())), new ItemName(secondResult.getItemName()); } <mapper namespace="jp.xxx.api.infrastructure.mappers.SampleProcedureMapper"> <resultMap id="FirstResultSet" type="jp.xxx.api.infrastructure.models.FirstResultSet"> <result property="id" column="Id"/> <result property="name" column="Name"/> </resultMap> <resultMap id="SecondResultSet" type="jp.xxx.api.infrastructure.models.SecondResultSet"> <result property="itemId" column="ItemId"/> <result property="itenName" column="ItemName"/> </resultMap> <select id="executeSampleProcedure" resultMap="FirstResultSet,SecondResultSet" statementType="CALLABLE" > {call SampleProcedure(#{id,jdbcType=BIGINT,mode=IN}, #{name,jdbcType=VARCHAR,mode=OUT})} </select> </mapper>
  10. © ZOZO, Inc. 技術選定(Doma 2)
 23 • 平均レイテンシ:19ms
 • コード量:ライブラリ内に必要なアノテーションが用意されており、コード量はかなり少ない


    ◦ ドメインオブジェクトへの変換もアノテーションにより可能
 @Procedure(name = "SampleProcedure") void execute( @ResultSet(ensureResultMapping = true) List<FirstResult> firstResults, @ResultSet(ensureResultMapping = true) List<SecondResult> secondResults, @In Id id, @Out Reference<OutputId> outputId); import org.seasar.doma.Domain; @Domain(valueType = Long.class, accessorMethod = "value") public record Id(Long value) { }
  11. © ZOZO, Inc. 24 技術選定のまとめ
 平均レイテンシ コード量 複数ResultSetのDB接続回数 Doma 2

    19 ms 少 1回 Spring Data JDBC 22 ms 多 1回 JPA 45 ms 中 n回 MyBatis 149 ms 多 1回
  12. © ZOZO, Inc. 部内でのJavaへの取り組み
 カート決済部
 • 事業案件を担当するチーム
 ◦ VBScriptを使用して開発
 ◦

    今後リプレイスしたコードも管理していく
 • リプレイスを担当するチーム
 
 事業案件を担当するチームでの取り組み
 • Java未経験者もいたが、外部講師によるJava講習会を受講して基礎を身につけた
 • チーム内でJavaを使用した勉強会を週に2回程度実施
 
 26