Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Seasar2で作った俺たちのサービスの今

19,655 views

Published on

2016/05/21(土) 17:00〜17:50 JJUG CCC 2016 Spring GH-6

2011年にリリースした弊社のサービスは、Seasar2ファミリーで構築しました。利用者数は1000万人以上となり、今もサービスとして成長しています。2016/9/26にSeasar2がサポートを終了するというアナウンスを受け、私たちもアクションを起こしました。

サービスには絶え間なく機能改善、機能追加の要件があり、その対応をしながらSeasar2から移行するという前提条件を考慮すると、選択肢は限られます。Scalaなど言語自体を変えることはなく、Javaのままとしました。新規アプリケーションを作成すると監視対象とするアプリケーションの数が増え、様々なコストが増えると考えたため新規とはしませんでした。こういったさまざまな条件のもと、まずはSeasar2で動作している機能から、まずリスクが小さい機能を新フレームワークで置き換え(ユニットテストも書き換え受け入れテストもし直し)てリリースしました。そして機能追加と並行して置き換えを進めています。

技術的には、移行対象としてはSpringを選択しました。Seasar2にある機能がそのままある場合もあれば、コードを書いて対応したものもあります。他に、S2DaoでのRDBMSへのアクセスを含んだビジネスロジックも、移行コストを最低限にするために資産としてSpringのコードからも利用できるようにしました。ユニットテストについてもS2TestCase とdjUnitから、単純作業として書き換えやすかったjMockitに移行しました。

セッションでは、こういった判断に至った経緯やその実現手法、メンバーへレクチャーなど移行のスタートから現時点のリリースまでにあるさまざまなことを話します。すばらしい判断でもなければすごい技術力で解決したわけでもない、普通なプロジェクトの現場で実際にやったことならではのリアルさを感じていただけると思います。

Published in: Technology
  • Be the first to comment

Seasar2で作った俺たちのサービスの今

  1. 1. 阪田 浩一 (さかた こういち) @jyukutyo じゅくちょー フリュー株式会社 元塾講師アルバイト 関西Javaエンジニアの会(関ジャバ) 会長!? 2009年秋 発足 2014年11月 JUG入り SI(客先常駐) 9年 / 自社サービス 5年目 blog : Fight the Future http://jyukutyo.hatenablog.com 昨年(2015年)のJJUG CCC 2015 Springでも話しました http://www.slideshare.net/jyukutyo/jjug-ccc-2015ab4
  2. 2. 2016/9/26にSeasar2は サポートを終了する! DBFlute DBFlute.NET Doma Emecha Mayaa S2Container.NE T S2Dao.NET 以下を除いた全プロダクトが EOLとなります!
  3. 3. つまりJavaでの Seasar2関連は ほとんどが対象
  4. 4. このセッションは、 それを受けて プロジェクトで 移行を始めた 事例の紹介です
  5. 5. 今日のゴール 対象 S2で開発/運用している人 提供したい こと 対象の人たちがS2から 移行する際の参考事例 注意事項 “ベスト”プラクティスでは ないこと
  6. 6. まずは結論から
  7. 7. 結果:Springに移行 2016年2月にリリース 移行前 移行後 プレゼンテー ション層 Cubby 2.0 Spring MVC 2.4.3 永続化層 S2Dao 1.0.51 Doma 2.6.0 コンポーネント フレームワーク S2 2.4.43 Spring 2.4.3
  8. 8. ただし、2月に リリースしたのは あくまで1機能のみ
  9. 9. リリース内容は 同一機能の リプレース
  10. 10. その後 さらにいくつか リプレースした
  11. 11. 2016年5月現在 3機能がSpringで 動作している
  12. 12. そんな状況です
  13. 13. 今日 お話しすること
  14. 14. 結果に至るまでのこと “なぜ”Spring/Domaなのか? “どのようにして”移行したのか? “何に”詰まった/困ったのか?
  15. 15. なぜSpring/Doma なのか?
  16. 16. WebSocketやSSEを(楽に)使いたい Java 8に移行したので、 それに対応するFWにしたい 長期間、同じ運用開発をしているので、 メンバーの飽きを防ぎたい ただし移行の緊急性は高くない (ように思う) そもそもS2から移行する 必要があるのか?
  17. 17. こうした理由に至った 僕らの背景を 簡単にご紹介します (あくまで理解の 補助として)
  18. 18. 対象アプリケーション サービス名 ピクトリンク サービス イン 2011年 (前身サービスはもっと以前から) 利用者数 1,000万人 女子高生の70%は会員 (有料会員数は秘密) 主な機能 プリントシール機で撮影した画像を 取得できる 提供 Webサイト iOSアプリ/Androidアプリ 課金 携帯電話キャリア課金 / 楽天ID決済 iOS課金 / Android課金 PageView 月間約1億PV
  19. 19. 備考:プリントシール機とは ゲームセンターや ショッピングモールにある 写真を撮影をする機械。 女性の利用が多い。
  20. 20. 開発チーム構成 サイトチーム 6名 • 私がリーダを担当 アプリチーム 7名 • iOS • Android • Web API インフラチーム 5名 • サーバ • ネットワーク • Ansible • Elasticsearch/Ki bana • Oracle RAC ログチーム 3名 • Tableau • BigQuery HTML/CSS 2名 • デザインをHTML に
  21. 21. 合計23名
  22. 22. 私はサービスインの 半年後に 参画しました
  23. 23. 23名という人数と 開発開始から 5年以上経過している、 そういう状況
  24. 24. 10年くらい標準だったよう S2、S2Struts、Cubby、S2Daoなど ただし、ここ5年ほど私の部署以外では 新規プロジェクトでScalaを採用 全社的に標準が S2ファミリーだった
  25. 25. 言語はJavaとする 運用開発チームの特性上、FWだけでなく 言語も変わると、メンバーが対応できない 日々の工数すべてを移行に避けない 機能改善/追加と並行に作業する アプリケーションを作り直さない テスト手順が煩雑なキャリア課金がメインのため 移行先FWの前提条件
  26. 26. All or Nothingの移行は やっぱり リスクが高すぎる! (ように思う)
  27. 27. まずは移行先となる フレームワーク(FW) を調査する
  28. 28. 移行先FWで調査した候補 Spring Spring MVC Spring Boot Java EE JSF JAX-RS Jersey MVC Play Framework Spark Framework Jodd Ninja Doma JOOQ JDBI Bootiful SQL Template A simple SQL template engine for Spring Boot
  29. 29. 考えたこと: もうトレードオフで 判断するしかない
  30. 30. S2、Cubby、S2Daoとそれほど 考え方が離れていないFWとする 学習にかけられる期間、工数を最小限に 今あるサービスクラスを (少なくとも多少コードを書けば) 利用できるFWとする トレードオフで重視したこと
  31. 31. Servletで動作するFWとする 既存のWebアプリと同一のWARファイルに 監視会社へ監視依頼する数が増えない コストが増えない 同一のアプリケーションとなりシンプル アプリケーション間で連携するのは複雑そう トレードオフで重視したこと
  32. 32. みなさんの プロジェクトでは、 どんな前提条件や どんなことを 重視しますか?
  33. 33. そして、 僕らの判断理由は
  34. 34. 決定:Spring MVC + Doma! プレゼンテー ション層 Spring MVC 2.4.3 Cubbyのアノテーションの 使い方と似てる感じ。 WebSocketに対応。 永続化層 Doma 2.6.0 S2Daoと同じく2-Way SQL。 Java 8に対応。 新規DaoはDomaで、既存 Daoは少しずつ置き換え。 コンポーネント フレームワーク Spring 2.4.3 S2Daoを含んだ既存資産は DIコンテナがある方が 利用しやすいと判断。
  35. 35. ちょっと詳細
  36. 36. Cubby -> Spring MVC
  37. 37. CubbyとSpring MVC似てる? Cubby @Accept(RequestMethod.GET) @Path("index") public ActionResult index() { return new Forward("/index.vm"); } Spring MVC @RequestMapping(path = "/index", method = RequestMethod.GET) public String index() { return "/index.vm"; }
  38. 38. Cubbyから移る 前提なら、 読めば内容が 理解できる近さ
  39. 39. S2Dao -> Doma 2
  40. 40. S2DaoとDomaのDaoクラス S2Dao @S2Dao(bean = Employee.class) public interface EmployeeDao { @SqlFile @Arguments({ ”employeeId” }) Employee findById(Integer employeeId); }
  41. 41. S2DaoとDomaのDaoクラス Doma @Dao @AnnotateWith(annotations = { // 生成されたDAO実装クラスに@Componentを付与する @Annotation(target = AnnotationTarget.CLASS, type = Component.class), // 生成されたDAO実装クラスのコンストラクタに // @Autowiredを付与する @Annotation(target = AnnotationTarget.CONSTRUCTOR, type = Autowired.class)}) public interface EmployeeDao { @Select Employee selectById(Integer employeeId); }
  42. 42. S2DaoとDomaの SQLテンプレート S2Dao select * from employee where /*IF employeeId != null */ employee_id = /*employeeId*/9999 -- ELSE department_id is null /*end*/
  43. 43. S2DaoとDomaの SQLテンプレート Doma select * from employee where /*%if employeeId != null */ employee_id = /* employeeId */9999 /*%else*/ department_id is null /*%end*/
  44. 44. 差異は少ない
  45. 45. Domaの 公式ドキュメントに DIコンテナとの 連携方法も 書いてある
  46. 46. どのようにして 移行したのか?
  47. 47. まず
  48. 48. 2014〜15年で Javaを 6 -> 8 Tomcatを 6 -> 8 へ移行しておいた
  49. 49. 基盤のバージョンアップは すでにクリアしている
  50. 50. ただし、Java 8の機能を 駆使したコードは まだなかった
  51. 51. 同時に当時から 少しずつ時間を取り、 メンバーに Java 8の機能をレクチャー
  52. 52. 新FWの学習だけを 進めればよい状態 としていた
  53. 53. さて、
  54. 54. 移行後 アプリケーションは こういう構造に
  55. 55. Application (WAR) S2Servlet DispatcherServlet DIコンテナ DIコンテナ Cubby Action Service / Logic S2Dao DAO Interceptor MVC Controller Service / Logic S2Dao DAO Interceptor Doma2 DAO 同一のJS/CSS/テンプレートファイル
  56. 56. 1つのアプリケーション 2つのDIコンテナ
  57. 57. DIコンテナそれぞれで 重複して オブジェクトを 管理することになる
  58. 58. なお、サーバ起動時間、 ヒープ使用量に 大きな差は出ていない
  59. 59. (だいたい シングルトンなので)
  60. 60. 新旧FWの振り分け Servlet Mapping / Filter /* /2/* 左記以外 Cubby ActionMVC Controller web.xmlで設定
  61. 61. 続いて 既存資産の利用
  62. 62. S2Daoを利用している Service / Logicを 既存資産として Springでも利用する
  63. 63. つまり、S2Daoの Daoオブジェクトを SpringのBeanとして 管理できればよい!
  64. 64. SpringでS2Daoを使う -その1- n-ichimuraの日記 2006年! http://d.hatena.ne.jp/n-ichimura/20061119/1163944881 過去にSpring + S2Daoを 検証した方が!
  65. 65. そのコードを参考に 現在の状況に合わせて 実装した
  66. 66. これで無事 SpringでS2Daoが 利用できた!
  67. 67. 続いて Spring MVCでの 課題
  68. 68. Cubby Actionから Spring MVC Controller へ書き換えるのは いいとして
  69. 69. リクエストパラメータ のバリデーションは どうしよう?
  70. 70. Cubby標準のバリデーションから Spring MVCでBean Validation利用へ JSR-349 Bean Validation 1.1 Method Validationを使う コントローラのメソッドを対象に リクエストパラメータの バリデーション
  71. 71. なぜ Method Validation を選んだのか?
  72. 72. Cubbyからの移行なので、 リクエストパラメータ をModelに入れる 方式に慣れていない (メンバーが多い)
  73. 73. サービスの性質上、 複雑なバリデーション はない
  74. 74. メソッドの引数に アノテーションを つけるだけの Method Validation でよさそう
  75. 75. という判断です
  76. 76. Cubbyでのバリデーション アクションクラス @RequestParameter(name = ”hoge”) public String hoge; protected ValidationRules rule = new DefaultValidationRules() { @Override protected void initialize() { add(”hoge”, new RequiredValidator()); } }; @Validation(rules = ”rule”, errorPage = ”/error.vm”) public ActionResult index() {
  77. 77. リクエストパラメータ を1つずつ インスタンス変数で 受け取っていた
  78. 78. メソッドバリデーション コントローラクラス @Controller @Validated public class HogeController { @RequestMapping(path = “validate", method = RequestMethod.POST) public ModelAndView validate(@NotEmpty String s) { .. }
  79. 79. Spring MVCでは リクエストパラメータを コントローラメソッドの 引数で受け取れる
  80. 80. この引数に対して アノテーションで バリデーションを 設定する
  81. 81. メソッドバリデーション コントローラクラス @Controller @Validated public class HogeController { @RequestMapping(path = “validate", method = RequestMethod.POST) public ModelAndView validate(@NotEmpty String s) { .. }
  82. 82. Bean Validation仕様と その実装Hibernate Validator独自の バリデーションの アノテーションは そこそこ用意されている
  83. 83. バリデーション用 アノテーション @AssertFalse/True @DecimalMax/Min @Digits @Future/Past @Max/Min @Null/NotNull @Pattern @Size @CreditCardNumber @Email @NotBlank @NotEmpty @Range @URL @ScriptAssert etc.
  84. 84. 独自のアノテーションも もちろん作れる
  85. 85. で、
  86. 86. バリデーションエラーに なったときは?
  87. 87. 例外 javax.validation. ConstraintViolationException が発生する
  88. 88. この例外を Springの ExceptionHandlerで 処理することにする
  89. 89. ExceptionHandler @ExceptionHandler(value=ConstraintViolationException.class) public ModelAndView handleValidationFailure( ConstraintViolationException e) { Set<ConstraintViolation<?>> v = e.getConstraintViolations(); ... // Cubbyにおけるバリデーション失敗時のJSON構造と合わせる. // バリデーション対象の変数名をキー、メッセージのリストを値としたMapに Map<String, List<String>> f = v.stream() .collect( toMap( c -> ((PathImpl)c.getPropertyPath()).getLeafNode().asString(), c -> { List<String> l = new ArrayList<>(1); l.add(c.getMessage()); return l; }
  90. 90. これでほぼOKなんだけど 困ったことが…
  91. 91. 最初SpringでMethod Validationが 動作せずちょっとハマった Bean Validationはメッセージリソース を利用できるが、”{0}は必須です”の {数字}の置換が使えない 困ったこと
  92. 92. SpringでMethod Validationを使う時は MethodValidationPostProcessor をBean登録する、 のだけど…
  93. 93. MethodValidationPostProcessor インスタンスの生成時に LocalValidatorFactoryBean インスタンスをvalidatorとして セットしましょう 公式リファレンスには はっきりそうと書いていない感じ SpringでMethod Validation が動作せずちょっとハマった
  94. 94. MethodValidationの Bean設定 @Bean public LocalValidatorFactoryBean l() { LocalValidatorFactoryBean l = new LocalValidatorFactoryBe... ReloadableResourceBundleMessageSource r = new ReloadableResourceBundleMessageSource (); r.setBasename("classpath:/messages"); l.setValidationMessageSource(r); return l; } @Bean public MethodValidationPostProcessor m(LocalValidatorFactoryBean l) { MethodValidationPostProcessor m = new MethodValidationPostProcessor(); m.setValidator(l); return m; }
  95. 95. Bean Validatorでメッセージに リソースバンドルの値は設定できる @NotEmpty(message=“{valid.required}”) Validationのアノテーションの属性値 はリソースバンドルから読める @Size(min = 2, message = ”{size.min}”) size.min={min}桁未満は入力できません Bean Validationでの リソースバンドルの利用
  96. 96. 今使っているこういうのができなさそう required={0}は必須です password=パスワード name=名前 組み合わせて... “パスワードは必須です”、“名前は必須です” Bean Validationでの リソースバンドルの利用
  97. 97. org.hibernate.validator.messageinterpolation. ResourceBundleMessageInterpolator を継承して実装した length={0}は{1}文字以下です password=パスワード five=5 @Size(message = ”{length}{0:password}{1:five}”) 出力:パスワードは5文字以下です 組み合わせられる実装を 作った
  98. 98. 難点:プロパティのキーを 複数使ったり、キー文字列 が長いと読みづらい message = "{valid.maxLength} {0:hoge.fuga.example.title}{1:max}"
  99. 99. よりよい方法があれば ぜひ教えてください〜
  100. 100. メッセージリソースは そのままSpring MVCでも 使えるようになった
  101. 101. ここでまったく 別の観点で課題が
  102. 102. 既存のユニットテスト が動かない!
  103. 103. 全クラスにユニットテストがある S2TigerTestCaseクラスを継承 EasyMock + djUnit + JUnit3 ユニットテスト
  104. 104. ラムダ式を書いたら djUnitの部分で エラーが発生!
  105. 105. java.lang.Error: djUnit class load error (Class : com.example.ExampleClass) at jp.co.dgic.testing.common.DJUnitClassLoader.findClass (ClassLoader.java:424) at jp.co.dgic.testing.common.DJUnitClassLoader.loadClass (DJUnitClassLoader.java:45) at java.lang.ClassLoader.loadClass (ClassLoader.java:357) at com.example.ExampleClassTest.setUp (ExampleClassTest.java:79) djUnitでエラーが発生
  106. 106. djUnitはもう 更新がないため、 Java 8に対応していない
  107. 107. 内部で利用している ASM/BCELの メジャーバージョンアップ に対応していない
  108. 108. 逆に言うと、 djUnitを使っていない ユニットテストなら 動作する
  109. 109. 新規/変更したクラスのユニットテスト は、クラス単位ですべて書き換えた JMockit + JUnit4 ユニットテストの書き換え
  110. 110. モックライブラリ 機能が豊富 APIの呼び出し方が少し変わっている JMockitとは
  111. 111. JMockit or Mockito + PowerMockで検討 JMockitの方が、djUnitを使ったテストコー ドを単純に書き換えやすそうに感じた JMockitにした理由
  112. 112. JMockitへの書き換え S2TigerTestCase + EasyMock + djUnit public class ExampleActionTest extends S2TigerTestCase { private ExampleAction tester; @EasyMock(register = true) private ExampleService service; public void setUp() { ... } public void recordIndex() { ... EasyMock.expect(service.something()) .andReturn(serviceResult); } public void testIndex() throws Exception { Forward actual = tester.index(); assertEquals("/hoge/fuga.vm”, ...); }
  113. 113. JMockitへの書き換え JMockit public class ExampleControllerTest { @Tested private ExampleController tester; @Injectable private ExampleService service; @Test public void testIndex() throws Exception { ... new Expectations(tester) { { service.something(); result = serviceResult; } }; String actual = tester.index(); assertThat(actual, is("/hoge/fuga.vm")); }
  114. 114. JMockitでの書き方が 見慣れない感じですね
  115. 115. しかし チームへの導入で とくに抵抗はなく、 みな書き換えができた
  116. 116. ちなみに、 移行した機能の いわゆる結合テストは すべて手で再実施…
  117. 117. 後の方で 発覚したこと
  118. 118. 「あ、S2Velocityの 機能も移行しないと いけないのか」
  119. 119. S2Velocityを使い、Veloctityツールの インスタンスをS2Containerで 管理していた Velocityツールとは テンプレートファイルで利用する ユーティリティクラス Velocityの利用
  120. 120. 同一のテンプレートファイルを Springのときでも動作させるためには 同様の仕組みが必要となる ToolboxManager/VelocityToolboxView を実装 SpringのコンテナからVelocityContextに Velocityツールのインスタンスを渡す仕組み を実装した Velocityの利用
  121. 121. これで既存の テンプレートファイルを そのままSpring MVCから 利用できるようになった
  122. 122. Tipsを2点
  123. 123. Tips(1) 既存クラス @Component(name = ”example”, instance = InstanceType.REQUEST) public class ExampleClass { スコープの設定 @Componentアノテーションを読み取って スコープ設定をするSpringのResolverを 作成
  124. 124. Tips(2) 既存クラス @Component(name = ”example”, instance = InstanceType.REQUEST) public class ExampleClass { Bean名の設定 @Componentアノテーションを 読み取ってBean名を生成する SpringのBeanNameGeneratorを作成
  125. 125. その他
  126. 126. 2時間のミーティング 主に以前との差分、新しい書き方や 意味を説明 ほぼ抵抗なく習得へ進んだ メンバーへのレクチャー
  127. 127. Springで動作させる機能を増やす 2016年5月現在で3機能なので 機能単位での独立したサービスにする マイクロサービス化? 企画要件へ利用できるようになった 技術を適用する WebSocketを使うとユーザにどんな体験を 提供できるか 今後の展望
  128. 128. S2からの移行、 怖くないよ!
  129. 129. ご清聴 ありがとうございました!
  130. 130. Q&A

×