ContractS開発者ブログ

契約マネジメントシステム「ContractS CLM」の開発者ブログです。株式会社HolmesはContractS株式会社に社名変更しました。

プロダクトにドメイン駆動設計を適用するためにはじめたこと

こんにちは。最近Slackのカスタム絵文字作りにハマっている友野です。Holmesでサーバーサイドエンジニアをしています。

Holmesが提供するホームズクラウドは、今年8月にサービスローンチ3周年を迎えました!

これまでの支持に感謝し、これからも長く使ってもらえるようにプロダクト改善に取り組んでいます。そのひとつとして、ドメイン駆動設計(以下、DDDと表記します)適用に関する取り組みについてご紹介します。似たような状況や同じ課題を持つ誰かの一助になれば幸いです。

背景と現状

ホームズクラウドPMF(Product Market Fit:プロダクトマーケットフィット)を経て、サービスシェア拡大のためにトレードオフスライダーをスピードに全振りしていました。Spring Bootを利用した三層+MVCアーキテクチャを採用し、ローンチ以来、機能追加・改善を繰り返し行ってきました。

日本ではCLM(Contract Lifecycle Management:契約ライフサイクルマネジメント)領域はまだ深く浸透しておらず、認知度向上のためにはとにかく市場評価する必要があり、スピード最優先にする戦略・アーキテクチャ採用は間違っていなかったと思います。

一方で、コアドメインである契約関連機能はその仕様の複雑さも相まって、コードの複雑性も増加の一途を辿り、メンテナンスコストが上がってきました。サービスローンチから3年をふりかえり、市場変化に合わせて「変更に強いプロダクト」を作ることがCTOはじめメンバー全員の共通認識になり、DDD採用を決める後押しになりました。

まずはじめたこと

採用を決めたものの、DDDとは何だろうか?というメンバーが多い状態から第一歩を踏み出すために、有識者の力が必要でした。

DDD Community JPを主宰している松岡さんに声をかけ、力を貸してくれるようお願いしたところ、快く引き受けてくれた上にライブモデリングとライブコーディングまでしてくれる運びとなりました(感謝しかありません)。それまでにメンバーは松岡さんのYouTubeで基礎知識をインプットし、モブワークに臨みます。

テキストベースのコミュニケーションをはさみながら、モブワークを2回ほど実施した後のメンバーの感想は以下の通りです。

  • モデルクラスにロジック集約してると動くドキュメント感あってよい
  • 正しい状態しか作れないと処理がシンプルになる!
  • 1クラスだけ見ればいいのは楽だし安心
  • ドメインやモデルが定義されていないソフトウェアからDDDを始めるには? 再設計/モデリング?

不安に思いながらも、多くのメンバーがその効果と威力に期待を膨らませ、モチベーションを高めていきました。

戦略的モデリング

先日の記事で触れたように、社内で勉強会を重ねた上で、仕様の共通理解と深掘りを行うためにドメインモデリングに取り組み始めました。具体的には、弁護士資格を持つ社員をドメインエキスパートとしてスクラムのリファインメントに招き、モデリングを実施しています。ドメインモデリング自体初めてのメンバーが多いため、以下の書籍を参考にモデリングの進め方ガイドを作り、適宜ふりかえりを交えながら進めています。

little-hands.booth.pm

現在リモートワーク中心での勤務体系のため、議論はオンラインホワイトボードmiro上で行い、モデリングをした後にバージョン管理のためにPlantUMLでまとめています。

そして、戦術的な設計

仕様の複雑性への対策として、ドメインモデリングだけでも一定の価値はあると考えています。これに加えて、戦術的な設計領域*1 まで踏み込むことでさらにDDD採用の効果を高め、変更に強い状態を作ることを目指しています。

しかし、既存の資産、かつ稼働しているサービスにおいて、どこから手をつけるか非常に悩ましい課題です。最初はドメインモデリングした成果を活かすために、集約をコードで表現することからスモールスタートすることに決めました。

採用するパターン2つ

集約をコードで表現するために以下の2つのプラクティスを採用します。

  • ドメインモデルを反映したオブジェクトを置くパッケージの作成
  • 既存テーブル構造に依存しないRepository+Adapterパターン

ドメインモデルを反映したオブジェクトを置くパッケージの作成

まず何より、ドメインモデルを反映したドメインオブジェクトを置く場所を決めます。既存のパッケージ構成では、Spring MVCやSpring Data JPAコンポーネントに合わせ、controllerserviceを中心に、entityrequestなどのモデルのパッケージを切っています。serviceパッケージのクラスには、データモデルが渡されて手続き的に処理されるコードがあるため、ドメインオブジェクトを配置するのは適切ではありません。ドメインオブジェクトを置くdomainパッケージを作ることにしました。serviceパッケージのクラスの修正は最小限に留め、ビジネスルールに関する操作をドメインオブジェクトに移譲する状態をゴールとしています。

  • serviceパッケージ内のクラスが担う責務
    • 全体の流れを構成する責務
    • データの取得、永続化(を依頼する)責務
  • domainパッケージ内のクラスが担う責務
    • ビジネスルールに基づく判断/加工/計算の責務

いわゆる三層アーキテクチャ+ドメインオブジェクトのパターンで、これは以下の書籍を参考にしています。

現場で役立つシステム設計の原則 〜変更を楽で安全にするオブジェクト指向の実践技法:書籍案内|技術評論社 gihyo.jp

既存テーブル構造に依存しないRepository+Adapterパターン

一方で、すでに稼働しているサービスではDB構造を大きく変更することは難しく、リスクも伴います。ホームズクラウドでは、データ永続化のライブラリとしてSpring Data JPAを採用しており、Repositoryインタフェースはあるものの、直接JPARepositoryを実装している*2のが現状です。つまり、JPAエンティティ(DDDのエンティティではないのでJPAをつけています)を渡さないと永続化できません。これでは既存のDB構造に強く依存してしまい、ドメインオブジェクトの構造自体が既存構造に引きずられかねません。

Repositoryインタフェースにドメインオブジェクトを渡したいので、JPA用のAdapterクラスを実装して、JPAエンティティへの変換と永続化責務を移譲することにしました。これで、既存資産のJPAエンティティへの依存を切ることが出来ました。DDDの文脈でのコンテキストマップにおいて腐敗防止層という考え方がありますが、これを既存概念(データモデル)に適用した形です*3

f:id:a-tomono:20201027000355p:plain
Repository以降で既存資産との変換を行う(※図はサンプル)

では、このAdapterクラスをどこに置くかというと、また新しくinfrastructureパッケージを追加しました。前節では三層アーキテクチャ+ドメインオブジェクトと記載しましたが、ここに関しては部分的に”依存関係逆転の原則”に従い、ヘキサゴナルアーキテクチャ(ポート&アダプター)の構造になっています。

ふりかえり

設計変更の効果と開発プロセスへの影響度合い、実装イメージの共有を目的として一部追加機能で実装を始めた段階です。結果が出るのはまだ少し先になりそうですが、既存のデータモデルに引きずられず、ドメインモデリングした結果をうまくコードで表現できていると思います。また、追加機能の実装を中心的に推進してくれたメンバーからは以下のようなコメントをもらっています。

  • 既存のテーブルに依存せず、本当に必要なものだけをドメインモデルで表すという考え方はとてもシンプルで分かりやすい。実際に出来上がったドメインモデルを見ると、必要な情報が思っていたより少なくて驚いた。ドメインモデルがシンプルなので処理の流れもシンプルになるという好循環。
  • Repository+Adapterの実装パターンのおかげで、既存のテーブルを気にせずドメインモデルをコードに起こすことに集中できた。
  • まだ理解しきれていない部分も多くあり、詰まる部分もあるが、実践していく中でドメイン駆動設計の良さに気づけることもあるので継続していきたい。

最も複雑、かつコアドメインである契約領域への適用は、ロードマップに従って順次進めていきます。並行して、本記事で触れていないプラクティスについても有効性を確認しながら適用していこうと考えています(少なくとも、ドメインサービスはかなり悩んだ/悩んでいるので、別途記事にできればと思います)。

まとめ

戦略的なドメインモデリングと並行して取り組んでいる、コードで表現する最小限のパターンをご紹介しました。本記事で触れた以外にも多くのプラクティスがありますが、すべてをすぐにプロダクトへ適用するのは難しいものです。なにより既存資産、既存サービスへの影響を考えながら、手段と目的を履き違えぬよう注意深く進めなくてはなりません。

◯◯をしたからDDDやってます!もなければ、△△していないのならDDDとは呼べない!というのはないと考えています*4

ドメイン知識を育てながら、愚直にオブジェクト指向プログラミングをするのが、一番のDDD成功への近道と言えそうです。

最後に

Holmesでは、今後もコアドメインに注力し、変更に強いプロダクト構築を通じて、お客様の契約業務に関する課題の解決とCLM市場拡大を進めていきます。 DDDに関して知見や興味があり、一緒にHolmesの目指す世界観を作りたい方、是非力を貸してください!

Holmesでは現在エンジニアを募集しています。 興味がある方は是非こちらからご連絡ください!

lab.holmescloud.com

*1:集約、値オブジェクト、エンティティ、ドメインサービス等のプラクティスやアーキテクチャなどを指します。詳細は以下の書籍をご参照ください。

エリック・エヴァンスのドメイン駆動設計(牧野 祐子 牧野 祐子 今関 剛 今関 剛 今関 剛 和智 右桂 和智 右桂 Eric Evans)|翔泳社の本 www.shoeisha.co.jp

*2:厳密にはJPARepositoryインタフェースを継承していて、Springが暗黙的に実装していますが、分かりやすさを優先しています。

*3:念のため補足すると、既存資産が腐っているという意味ではありません。念のため…。

*4:さすがにドメインを無視してDDDだ!はないとは思いますが…

WebシステムのXcodeによるモバイル確認についてまとめました(デザイン、動作)

こんにちは、Holmesでエンジニアをしている柳沢です。

今回はモバイルのデバッグ方法について書こうかと思います。 皆さんはどのように確認していますか?

多数の人はWebブラウザに搭載されているデベロッパーツールを利用していると思います。
バイスを選択できたり、縦横比をいれるだけで簡単に確認できるのはいいですよね。
ただ、JavaScriptの挙動やデザインなどを見るとなると物足りないと思うはずです(レスポンシブの動作を見るにはまだ良いのですが、デザインがかなり異なる場合やジェスチャ操作、カメラ、ファイル選択、シェアAPIのエミュレートなど)。
やはり実機、、でも揃えられるわけないじゃん!
ということで、iOSXcodeAndroidAndroid Studioに落ち着くわけです。

XcodeAndroid Studioはそもそもネイティブアプリ開発用のIDEです。ですので開発したものがモバイルでどうなっているのかなんて当然わかるわけです。
その開発用のIDEの機能の一部を使用し、シミュレートするのです。これは頼り甲斐がありますねー 。

そこで需要の高いiOSXcodeでのデバッグを少し詳細に書いてみようと思います。

操作

現在、XcodeMacOSバージョン10.15.4以上でないとインストールできないようです。
https://apps.apple.com/jp/app/xcode/id497799835?mt=12
上記よりApp Storeにいきダウンロードします。

起動するとプロジェクトを作る画面が表示されますが無視して、ウィンドウにあるアプリケーションメニューよりXcode → Open Developer Tool → Simulatorを選びます。

シミュレーター
シミュレーター

初期ではiPhoneが表示されますが、File → Open Simulatorで切り替えることができます。

デバイス切替
バイス切替

ipadシミュレーター
iPadシミュレーター

Safariを起動して開発よりシミュレーターを選択するとデバッグできたのですが、
現在ではSafari Technology Previewという最新の技術を試験実装したプレビュー版に統合されたようですね。
こちらをダウンロードして起動すれば今までと同じ操作、Develop → Simulator → デバッグしたいページを選択で見ることができます。

safari technology preview
safari technology preview

*先にSafariを立ち上げていると開発にシミュレーターが出てきませんので再起動しましょう

リモートデバッグ
リモートデバッグ

所感

ネイティブアプリ開発用ツールなので実機のように挙動やデザインが確認できることはすばらしいですね。 ただ容量が結構必要なことと、動作が重いのは難点ですかね。。

それではまた!

最後に

Holmesはエンジニア・デザイナーを募集しています!
興味がある方はこちらからご連絡ください!

lab.holmescloud.com

lab.holmescloud.com

WebDavでWord直接編集をしてみる

こんにちは、尿管結石を再発させてしまったid:c-terashimaです
数年ぶりに味わう痛みはやはり辛いですね。。皆さんも健康に気をつけて楽しいエンジニアライフを送っていきましょう

文章を書くにはWordを利用されている方が多く、契約書を作成するにも今使っているWordをそのまま使いたいというご要望をいただくことがあります
Wordを直接編集したら弊社ホームズクラウドへ登録される仕組みが可能か検討したところ、WebDavを使えばイケるのではないか?ということで早速試してみました

環境

次の環境で動作確認を行っています

  • Java8
  • SpringBoot
  • SpringSecurity
  • Word for Mac

TomcatWebDavを起動させる

ホームズクラウドはSpringBootで動作しているため、SpringBoot(Tomcat)上でWebDavを動作させる必要があります
TomcatWebdavServletクラスが用意されていますのでこちらを使っていきます

@SpringBootApplication
public class Application extends SpringBootServletInitializer {
    public static main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    public TomcatServletWebServerFactory servletContainer() {
        final TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
        factory.setDocumentRoot(new File(documentRoot));
        return factory;
    }

    @Bean
    public ServletRegistrationBean servletRegistrationBean() {
        // WebDavエンドポイントの設定
        ServletRegistrationBean bean = new ServletRegistrationBean(new WebdavServlet(), "/webdav/*");

        bean.setName("WebDav");
        bean.addInitParameter("debug", "1");
        bean.addInitParameter("listings", "true");
        bean.addInitParameter("readonly", "false");

        return bean;
    }

    @Bean
    public StrictHttpFirewall httpFirewall() {
        StrictHttpFirewall firewall = new StrictHttpFirewall();
        firewall.setAllowedHttpMethods(Arrays.asList(
                "HEAD", "DELETE", "POST", "GET", "OPTIONS", "PATCH", "PUT",
                "PROPFIND","PROPPATCH", "MKCOL", "COPY", "MOVE", "LOCK", "UNLOCK"
        ));
        return firewall;
    }
}

servletContainerメソッドでドキュメントルートをしており、Wordファイルが配置されるパスとなります。実際運用する場合は、クラウドストレージにファイルを格納することになります
servletRegistrationBeanメソッドでServletクラスの登録とエンドポイントを設定しています。ファイルアクセスする際に使われます
WebdavServletクラスにパラメータを設定する必要があります

  • debug:debugログの出力を制御
  • listings:ファイルリストの参照を制御。これをONにしないとファイル操作ができない
  • readonly:ファイルの書き込みを可能にする

WebDavでは独自のHttpMethodを利用するためSpringSecurityを利用していると弾かれてしまうため、登録します

メソッド 機能
PROPFIND プロパティの取得
PROPPATCH プロパティの変更
MKCOL コレクションの作成
COPY コレクションを含むリソースおよびプロパティの複製
MOVE コレクションを含むリソースの移動
LOCK コレクションを含むリソースのロック
UNLOCK コレクションを含むリソースのロック解除
OPTIONS 既存のメソッドと同じ
GET 既存のメソッドと同じ
HEAD 既存のメソッドと同じ
POST 既存のメソッドと同じ
PUT リソースの作成
DELETE コレクションおよびそのコレクションに含まれるリソースの削除

サーバ側の設定はこれだけです

ブラウザからアクセス

Office URI スキーマでブラウザからWebDavにアクセスを行いWordを開きます

Office URI スキーマとは

このドキュメントでは、オフィス生産性向上アプリケーション用の Uniform Resource Identifier (URI) の形式を定義します。このスキームは、Microsoft Office 2010 Service Pack 2 以降でサポートされており、これには Microsoft Office 2013 for Windows および Microsoft SharePoint 2013 などの製品が含まれます。また、Office for iPhone、Office for iPad、および Office for Mac 2011 でもサポートされます。

Office URI スキーマ | Microsoft Docsより引用

URIスキーマの仕様に則って ms-word:ofe|u|http://localhost:8080/webdav/test.docxにアクセスするとWordを起動するかを問うダイアログが表示されますので Microsoft Word.app を開くをクリックしてください。WebDavに公開されているWordが開きます
そのまま、編集し保存を行うと自動でサーバにアップロードされます

f:id:c-terashima:20201008190029g:plain

まとめ

SpringBoot(Tomcat)でWebDavは結構かんたんに実装することができました
ただ、最低限の動きを検証しただけなのでセキュリティなどには十分注意してください
ホームズクラウドにWordの直編集機能が組み込まれるのは近い!?







Holmesはエンジニア・デザイナーを募集しています!
興味がある方はこちらからご連絡ください!

lab.holmescloud.com

lab.holmescloud.com

PlantUMLでドメイン駆動設計のモデリングを実装する(Nizi Project編)

こんにちは!株式会社Holmesでエンジニアをしている平田です。

Holmesでは、現在、プロダクト開発にドメイン駆動設計を取り入れようと、社内で勉強会の開催や各メンバーが勉強したことを共有しあったりしています。
ドメイン駆動設計に取り組むにあたって、大切なことのことの一つにより良いモデルを作成する、モデリングがあります。
今回はそんなモデリングをPlantUMLで行う方法について、一例をご紹介できればと思います。

※本記事のモデリング手法は以下の書籍を参考に行なっております。

PlantUMLとは

PlantUML(github)とは、オープンソースUMLダイアグラム作成用の言語です。
本記事ではVisual Studio Code拡張機能を用いています。(PlantUMLのセットアップは拡張機能の説明をご覧ください)

なぜPlantUMLを用いるのか

モデリングを行う方法はオンライン・オフライン問わずいくらでも存在すると思います。
弊社でも、オンラインホワイトボードのmiroを使ったりしています。
その中で、PlantUMLを使う利点としては、プレーンテキストで記述できるため、Gitなどのバージョン管理システムで管理できることではないでしょうか。 PlantUMLで記述したモデルをバージョン管理することによって、コードレビューのプロセスに乗せたり、変更を追っていくことができるようになります。

ユースケース

まずはユースケースを作成します。
今回は、Nizi Projectドメインとして作成します。

Nizi Project(ニジプロジェクト)は、韓国大手事務所JYPエンターテイメントとソニーミュージックによる共同ガールズグループプロジェクトである。

(Wikipediaより)

このオーディションに合格したメンバーはNiziUとして2020/12/02にデビューします。(ついでに覚えて帰ってね)

全体像

f:id:k-hirata:20201007012400p:plain
ユースケース

@startuml NiziProject
actor プロデューサー
left to right direction
rectangle {
    プロデューサー -- (キューブをあげる)
    プロデューサー -- (テストを進める)
    プロデューサー -- (パート1脱落者を決める)
    プロデューサー -- (パート2脱落者を決める)
    プロデューサー -- (合格者を決める)
}
@enduml

たったこれだけでユースケース図を作成することができます。
記述内容の詳細を説明します。

@startuml,@enduml

これは開始と終了をそれぞれ表しています。

@startuml
@enduml
actors
f:id:k-hirata:20201008223846p:plain
actor プロデューサー

actor プロデューサーで人の形をした図が表示されラベルを設定することができます。
:プロデューサー:と記述することもできます。

usecases
f:id:k-hirata:20201008223950p:plain
:プロデューサー: -- (キューブをあげる)
f:id:k-hirata:20201008224037p:plain
left to right direction

プロデューサー -- (キューブをあげる)ではユースケースとactorとの関連を表現しています。--と記述することで関連を線でつなぐことができます。
ユースケースusecase キューブをあげると書くこともできますが、今回は定義と関連付けを同時に行いたかったので(キューブをあげる)と記述しています。
left to right directionは関連の方向を指定しています(作成したい図に合わせて調整してみてください)。

rectangle
f:id:k-hirata:20201008231932p:plain
rectangle { :プロデューサー: -- (キューブをあげる) }

rectangle {}はブロック内に記述することで枠に囲われたように表現することができます。見た目やスコープのために記述しています。

ドメインモデル図

ドメインモデル図は冒頭に紹介した書籍に沿ったルールで作成します。

  • ルールを吹き出しに記述する
  • オブジェクトの関連を記述する
  • 多重度を記述する
  • 集約の範囲を記述する
全体像

f:id:k-hirata:20201008011105p:plain
ドメインモデル図

@startuml NiziProject
skinparam PackageStyle rectangle

package 候補者集約 {
    object 候補者 {
        候補者ID
        パート1脱落
        パート2脱落
        合格
        オーディションID
    }
    object 名前 {
        姓
        名
        ニックネーム
    }
    object ペンダント {
        パート1キューブ
        パート2キューブ
    }
    object キューブ {
         キューブ名
    }
}

note left of ペンダント
    * ペンダントにはそれぞれ4つのキューブをはめることができる。
    * パート1用キューブは重複できず決められた4種類、
      パート2用キューブは最大4つはめることができる。
end note

note bottom of キューブ
    * キューブは5種類存在する。
    * パート1キューブ(「ダンス」「ボーカル」「スター性」「人柄」)
    * パート2キューブ
end note

note right of 候補者
    * パート1で脱落した候補者はパート2キューブを獲得できない
    * パート2で脱落した候補者も以降、パート2キューブを獲得できない
end note

名前 "1" -left-* "1" 候補者
ペンダント "1" -down-* "1" 候補者
キューブ "0..8" -down-* "1" ペンダント

package オーディション集約 {
    object オーディション {
        オーディションID
        オーディション名
        現在テスト
    }
    object テスト {
        テスト名
        パート
        順序
    }
}

note right of オーディション
    * テストの順番は決められている。
end note

note right of テスト
    * テストは1オーディションにつき、パート1に4回、パート2に4回ある
    * テストで獲得可能なキューブは決められている。
    * パート1テスト1~3は決められたパート1キューブを1つ、
      テスト4ではパート1キューブ最大4つ獲得できる。
    * パート2テスト1~3まではパート2キューブ1つ、
      テスト4ではパート2キューブ最大4つ獲得できる。
end note

候補者 "1..n" -down-> "1" オーディション
テスト "8" -down-* "1" オーディション
テスト "1..4" -down-> "1..4" キューブ

@enduml

記述内容の詳細を説明します。

packages
f:id:k-hirata:20201008233415p:plain
package 候補者集約 {}
f:id:k-hirata:20201008233433p:plain
skinparam PackageStyle

skinparam PackageStyle rectangleはパッケージのスタイルを変更するために記述しています。様々なスタイル変更を施すことができるので、興味がある人は調べてみてください。

package 候補者集約 {}集約を表現しています。ブロック内に記述することで集約内であることを表現できます。

objects
f:id:k-hirata:20201008233704p:plain
object 候補者 { 候補者ID }
f:id:k-hirata:20201008234121p:plain
package 候補者集約 { object 候補者 }

object 候補者 {}はオブジェクトです。ブロック内には属性を記述することができます。

notes
f:id:k-hirata:20201008235304p:plain
note left of ペンダント: コメント
f:id:k-hirata:20201008235322p:plain
改行できる

吹き出しでルールを記述することができます。以下は改行の表現で、end noteまでの間に文章を記述し、改行が反映されます。また、インデントが揃っていれば、吹き出しはインデントされませんが、インデントがずれている場合、最もインデントが少ない行をスタートとしてインデントが反映されます。改行が不要な場合、note left of ペンダント: コメントと簡潔に記述することもできます。

note left of ペンダント
  改行
  できる
end note
direction
f:id:k-hirata:20201008235710p:plain

left吹き出しをオブジェクトに対してどの方向に表示するかを指定できます。(left、right、top、bottom)

relations
f:id:k-hirata:20201009000912p:plain
名前 "1" -left-* "1" 候補者
f:id:k-hirata:20201009002700p:plain
名前 "1" -right-* "1" 候補者

名前 "1" -left-* "1" 候補者は多重度とオブジェクト間の関連を表現しています。
-left-*は関連になりますが、--の間にleftと入れることで関連を表示する方向を決めることができます。(left、right、up、down)

label

f:id:k-hirata:20201009002806p:plain
候補者 "0..n" -down-> "1" オーディション
終端の*はいくつかの種類があり、始端と終端両方に記述が可能です。ドメインモデル図では*または>しか登場しません。
関連付けるオブジェクトと関連付けの間に""(ダブルクォート)で囲んで文字を記述することで多重度の表現ができます。

振り返り

私自身、この記事で初めてモデリングを行いました。

ドメインモデル図を見ていただらいたらわかるように、テストのルールが多かったり、キューブがテスト・候補者の双方に深く関連しているように見えます。
今回は、「キューブをあげる(候補者がキューブを受け取る)」ユースケースを鑑みてこのようなモデリングを行いましたが、ユースケースや今後の展開によっては、キューブ集約やテスト集約といった形に変更していくことも考えられます。

モデリングは最初に作成したら終わりではなく、コード同様、常に変更し、より良いモデルに改善していくことが重要です。
モデルをPlantUMLで記述してバージョン管理していくメリットが活きてきますね!

最後に

いかがでしたでしょうか。
PlantUMLを用いたドメイン駆動設計のモデリングが容易にできることがわかっていただけたと思います。
是非活用していただければ幸いです。

Holmesではエンジニア・デザイナーを募集しております。ご興味がある方はこちらからご連絡ください。

lab.holmescloud.com

lab.holmescloud.com

Nuxt.js上でFroala Editorを動作させる方法

Holmesでエンジニアをしている三澤です。

弊社のサービスであるホームズクラウドには、契約書のテンプレートを自由に作成することができる機能があります。この機能を実現する上で、軽量なJavaScript リッチテキストエディターのFroala Editor(公式サイト)を利用しています。Froala Editorは、vue.js版も提供されており、vue.jsで動作するサンプルはあるのですが、Nuxt.js上で動作するサンプルが見当たらなかったので忘備録として残したいと思います。

本投稿では、まず①Froala Editorを表示させます。その後、②独自カスタマイズボタンを追加させたいと思います。 ポイントは、FroalaEditorをプラグイン化して呼び出すこと、独自カスタマイズボタンを追加する場合、DOM操作が行われる処理を実行するタイミングに注意することです。

環境

  • macOS Catalina
  • node v12.16.1
  • npm 6.13.4

前提

create-nuxt-appでNuxt.jsの環境構築済み(SSRモード(※サーバーサイドレンダリングモード)、すべてデフォルト設定)

インストール

Froala Editor

npm install vue-froala-wysiwyg

※本記事作成時にインストールされたバージョンは3.1.0でした。

実装

まずFroala Editorを表示させることを実現したいと思います。

Froala Editorをプラグイン

続いてpluginsディレクトリ内に下記のファイルを作成します。 このファイルと、次項のnuxt.config.jsの編集によってどのコンポーネントからもFroala Editorを呼び出すことができるようになります。

  • ファイル名:froala.js

  • 場所:plugins/froala.js

//froala本体インポート
import 'froala-editor/js/plugins.pkgd.min.js';
//サードパーティプラグインインポート
import 'froala-editor/js/third_party/embedly.min';
import 'froala-editor/js/third_party/font_awesome.min';
import 'froala-editor/js/third_party/spell_checker.min';
import 'froala-editor/js/third_party/image_tui.min';
//cssインポート
import 'froala-editor/css/froala_editor.pkgd.min.css';
 
import Vue from 'vue'
import VueFroala from 'vue-froala-wysiwyg'
import 'froala-editor/js/froala_editor.pkgd.min.js'
Vue.use(VueFroala)
Vue.config.productionTip = false

nuxt.config.jsの編集

前項でプラグイン化したFroala Editorを呼び出せるようにします。 併せてjqueryを使用できるようにします。

//省略
/*
** Plugins to load before mounting the App
*/
plugins: [
  { src: '~/plugins/froala.js', ssr: false } //追加
],
//省略

これで準備は完了です。 これからFroala Editorを呼び出すコンポーネントを作成していきます。

Froala Editorを呼び出すコンポーネントを作成

Froala Editorはコンポーネント化しなくても、ページ内に直接タグを記述してもいいのですが、エディターのヘッダー定義や独自カスタマイズボタンをする場合、複数の箇所で記述するのはよろしくないのでコンポーネント化し、それをページから呼び出したいと思います。

ここで行っている処理の概要は下記の通りです。

  1. templateタグ内で、froalaと内部変数をバインディング

  2. propsで初期表示する文字列を親コンポーネントから受け取る

  3. data定義でFroala Editorのヘッダーボタンの定義やプレイスホルダー、イベントハンドラの定義

  4. created処理でFroala Editorで入力した文字列を内部で保持するモデルにpropsで受け取った初期表示の文字列を設定

<template>
  <div>
      <froala id="edit" :tag="'textarea'" :config="config" v-model="content"></froala>
  </div>
</template>

<script>

export default {
  props: {
    initContent: String
  },
  data () {
    return {
      config: {
        toolbarButtons: {
          'moreText': {
            'buttons': 
                 [
                     'bold', 'italic', 'fontSize','underline', 
                     'strikeThrough', 'textColor', 'backgroundColor',
                     'inlineClass','inlineStyle','clearFormatting'
                 ],
            'buttonsVisible': 3
          },
          'moreParagraph': {
            'buttons': 
                 [
                     'alignLeft', 'alignCenter','alignRight', 
                     'alignJustify', 'formatOLSimple', 'formatOL', 
                     'formatUL', 'lineHeight', 'outdent', 
                     'indent', 'quote'
                 ],
            'buttonsVisible': 3
          },
          'moreRich': {
            'buttons': ['insertImage', 'insertTable',
                              'specialCharacters', 'insertHR'],
            'buttonsVisible': 3
          },
          'moreMisc': {
            'buttons': ['undo', 'redo','insert'],
            'buttonsVisible': 3
          }
        },
        placeholderText: '入力してください',
        events: {
          initialized: function () {
            console.log('initialized')
          },
          input: function () {
            console.log('inputed')
          }
        }
      },
      content: null
    }
  },
  created : function () {
      this.content = this.initContent
  },
}
</script>

上記で作成したコンポーネントを呼び出すページを作成

先ほど作成したFroalaView.vueを呼び出すページを作成します。

  • ファイル名:index.vue

  • 場所:pages/index.vue

ここで行っている処理の概要は下記の通りです。

  1. templateタグ内で、FroalaViewタグにpropsとして初期表示文字列を渡す

  2. import構文で先ほど作成したコンポーネントを読み込む

  3. componentsプロパティでFroalaViewコンポーネント使用を宣言

  4. dataでエディターに渡す初期表示文字列を定義

<template>
  <div class="container">
    <div class="editor">
      <FroalaView :initContent="editorContent"></FroalaView>
    </div>
  </div>
</template>

<script>
import Vue from 'vue'
import FroalaView from '~/components/FroalaView'

export default {
  components: {
    FroalaView
  },
  data: () => ({
      editorContent: 
            'こんにちは、契約マネジメントシステム「ホームズクラウド」の開発者ブログです。'
  })
}
</script>

<style>
.container {
  margin: 0 auto;
  min-height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  text-align: center;
}
.editor {
  width: 800px;
}
</style>

動作確認(Froala Editorが表示されること)

アプリケーションを実行し、localhost:3000にアクセスして確認します。

f:id:a-misawa:20200927205405p:plain

Froala Editorが表示されました。続いて、エディターのヘッダーに独自カスタマイズボタンを追加したいと思います。今回はFroala Editorのヘッダーにマークボタンを追加し、クリックすると、spanタグを挿入する、ということを実現したいと思います。

独自カスタマイズボタンを追加

カスタマイズボタンを追加する場合、Froala Editorを呼び出したコンポーネントに処置を追加します。

// 省略 created()の直下に、下記コードを追加
mounted : function () {
  const FroalaEditor = require ('froala-editor')
  FroalaEditor.DefineIcon('insert', {NAME: 'plus', SVG_KEY: 'add'});
  FroalaEditor.RegisterCommand('insert', {
    title: '要素を追加',
    focus: true,
    undo: true,
    refreshAfterCallback: true,
    callback: function () {
      this.html.insert('<span class="contractor-seal">挿入された要素</span>');
    }
  });
}
// 省略 最下行に下記コードを追加
<style>
  .contractor-seal {
    color : red;
    width: 65px;
    height: 65px;
    border: 1px solid red;
    border-radius: 4px;
    text-align: center;
  }
</style>

※注意点として、Nuxt.jsプロジェクト作成時にSSRモードで作成した場合、サーバー内でデータを取得してレンダリングしてくれます。しかしFroala Editorに独自のカスタムボタンを追加する場合、Frola Editor内でDOM操作が行われており、サーバーサイドで実行されると内部エラーが発生します。それを回避するためにFroala Editorの処理を実行するのはmounted(DOM作成後=クライアントサイド)にて行っています。

動作確認(独自カスタマイズボタンが追加され、ボタンが機能すること)

カーソル位置にタグを挿入されました

独自カスタマイズボタンが追加され、クリックすると正しくカーソル位置にタグが挿入されることが確認できました。

所感

このように簡単にNuxt.jsプロジェクトでFroala Editorを起動することができることがわかりました。注意点として、Vue.js(Nuxt.jsでも)でDOM操作を行うことは本来あまり勧められていませんがどうしても必要な時もあります(どうしても以前開発したjQueryで記述された処理を移行させたい、jQueryで動作するプラグインを動かしたいなど)。その場合、SSR環境の場合はDOM操作が行われるタイミングに注意する必要があります。

以上、Nuxt.js上でFroala Editorを動作させる忘備録でした。

最後に

Holmesはエンジニア・デザイナーを募集しています!
興味がある方はこちらからご連絡ください!

lab.holmescloud.com

lab.holmescloud.com

SpringBootのアノテーションを使う際につまずいたポイント

Holmesの倉島です。サーバーサイドの実装をしている最中に度々存在する、@(アノテーション)とそれに関連するエラー に頭を悩ませています。今回はそんな一例を紹介しようと思います。

@RestController

とあるControllerクラスに@RestControllerをつけたところ、実行時にエラーになってしまった!

新たにパッケージ内にControllerクラスを作成し、@RestControllerをつけたところテスト実行時に「ApplicationContextが読めない!」という旨のエラーが出てしまった。

java.lang.IllegalStateException: Failed to load ApplicationContext


結論を述べると、どうも同じ名前のControllerクラスが別パッケージ内に存在しているのでエラーになっていたらしい。成程それならばどちらかのControllerクラスの名前を変更すれば解決できそうです!余談として、どうしてもControllerクラスの名前を変更したくない場合は@RestControllerと同時に@Bean(name="hoge”)を付けても解決できるようです。

// before
@RestController

// after
@Bean(name="hoge")
@RestController

("hoge"の部分がSpring内での管理名になる。ちなみに"hoge"の部分は他のControllerクラス名と被っては同じ轍を踏むことになるので注意)

@SpringBootApplication

Entityクラスを、今まで保存している場所とは別のパッケージに作成したが、何やらエラーになってしまった...。

こちらも実行時にエラーになってしまった。

Caused by: org.springframework.boot.web.server.WebServerException: Unable to start embedded Tomcat

私はどうしても別パッケージにEntityクラスを作成したいのです!
解決法を求め調べを進めていくと、@SpringBootApplicationが付いているクラスにEntityクラスを保存すべきパッケージが設定されているらしい。
私はJPAを使用しているので、@SpringBootApplicationが付いているクラスにはEntityManagerFactoryが作成されるクラス名が指定してありました。そのクラスに念願のパッケージ指定箇所がありました!あとは、setPackagesToScanでパッケージのパスを指定して、晴れてEntityクラスを別パッケージに保存することができました。(ちなみに、repositoryクラスの保存すべきパッケージも@SpringBootApplicationが付いているクラスで指定できる)

@NamedQuery

Entityクラスの先頭部に記述されていた。エラーは出ていないが一体どういう意図で使用されているのだろう?

Entityクラスに、@Entityや@Data(lombok)と同じように@NamedQueryが@NamedQuery(name = "hogeTable.findAll", query = "SELECT h FROM hogeTable h") このように指定されていた。

@Entity
@Data
@NamedQuery(name = "hogeTable.findAll", query = "SELECT h FROM hogeTable h")
public class hogeTable { 

どうも、nameに設定されている名前を指定することによりqueryに設定されているsql文を実行することができるらしい。 別途Repository(jpa)を作成していた場合は、上記の例だと hogeTbaleRepository.findAll を指定することにより、SELECT h FROM hogeTable h の結果を取得することができる。(Repositoryを作成しない場合の指定方法は、EntityManagerクラスのcreateNamedQueryを使用する。EntityManager.createNamedQuery("hogeTable.findAll")のように指定する)

おわりに(感想)

結論(感想)としては、@(アノテーション)は理解した上で使用するのであれば面倒なことを考えなくていいので便利!と感じる反面、一見すると意味がわかりづらく保守性が下がる可能性があるのでは?という面もありました。過ぎた力は身を滅ぼしますね。
なんとなく表面だけ理解した状態で、言葉足らずの説明でしたが、参考になれば幸いです。

Holmesはエンジニア・デザイナーを募集しています 興味がある方はぜひこちらからご連絡ください!

lab.holmescloud.com

lab.holmescloud.com

gilotによるGitログ解析を、複数Gitリポジトリに対して試してみた

Holmesでエンジニアをしている山本です。

gilot (ジロー)の紹介記事を見て、複数チーム・複数Gitリポジトリで開発している弊社ではどのような結果が得られるかと思い、試してみました。

qiita.com

実行環境

名前 バージョン
OS Windows 10 Pro 64bit バージョン2004
Python 3.8.5
Git 2.28.0.windows.1
gilot 0.2.4

参考

Qiitaの記事gilotのGitHubリポジトリ、また以下の記事を参考にしました。

解析対象

解析対象とするリポジトリについて

現在、弊社では、複数のGitリポジトリに対して複数チームが開発を行っています。

今回は、Javaによるバックエンド、Vue.jsによるフロントエンドを対象とし、それぞれ名前を backend, frontend とします。対象とするブランチは、いずれもmasterです。

以下のディレクトリ構造にて、 root ディレクトリをカレントディレクトリとしてコマンドを実行していきます。

root/
  ├ backend/
  └ frontend/

hotspotやhotgraphで対象とするファイルパスについて

hotspotやhotgraphでは、ユニットテストなどのファイルを含めると結果が正しく取れないため、対象とするファイルパスを指定します。

backendは、プロジェクト管理ツールとしてGradleを使用したSpring Bootアプリケーションのため、解析時には --allow-files 'src/main/*' を指定します。

frontendは、複数パッケージを packages/* 配下で管理しており、その中にStorybookやJestのテストファイルが含まれるため、解析時には --allow-files 'packages/*' --ignore-files '*/__tests__/*' '*.spec.ts' '*.stories.js' を指定します。

gilotによる解析

インストール

GitとPython3はインストール済みのため、 pip install gilot でインストールできました。

gilotのインストールに成功すると、バージョンが表示されます。実行環境にも記載しましたが、記事作成時点でのバージョンは0.2.4です。

Installing collected packages: gilot
Successfully installed gilot-0.2.4

また、 author コマンドによるグラフ描画に使用されるということで、IPAゴシックフォントをインストールしておきます。

IPAフォントのダウンロード

log

まずはログの取得を行います。 hotspothotgraph では --full オプションをつけて出力しておく必要があります。

後の解析コマンドにファイルを渡しやすいよう、出力ファイルにはプレフィックスとして log- を付与しておきます。また、frontendの開発が始まったのが2020年6月のため、 --since 2020-06-01 で開始日を指定します。

gilot log --full -b master -o log-backend.csv --since 2020-06-01 --full backend/
gilot log --full -b master -o log-frontend.csv --since 2020-06-01 --full frontend/

backendは約1,300行の出力に1分ほど、frontendは約2,100行の出力に2分ほどかかりました。

出力にはマージコミットも含まれますが、除外せずそのまま解析を行います。

plot

plot コマンドにて、グラフ生成を行います。

開発手法としてスクラムを採用しており、スプリント期間は各チーム1週間となっているため、解析時のタイムスロットを -t 1w で1週間とします。

backendとfrontend、両方合わせたものと個別で3種類出力してみます。

gilot plot -i log-*.csv -t 1w -o plot-all.png
gilot plot -i log-backend.csv -t 1w -o plot-backend.png
gilot plot -i log-frontend.csv -t 1w -o plot-frontend.png

それぞれの結果は、以下のようになりました。

f:id:h-yamamoto_holmescloud:20200904162547p:plain
plot-all

f:id:h-yamamoto_holmescloud:20200904162603p:plain
plot-backend

f:id:h-yamamoto_holmescloud:20200904162620p:plain
plot-frontend

frontendは6月から開発を開始したため、環境構築時のコミット行数が多く、かなり上振れしたグラフになっておりました。出力期間の変更が必要そうです。

アウトプットの落ち込みがある部分は、お盆休みの週となります。

hotspot

hotspot コマンドにて、変更頻度の高いファイルのランキングを出力します。ユニットテスト関連ファイルなどを除外するため、--allow-files および --ignore-files を指定しております。

gilot hotspot -i log-backend.csv \
  --allow-files 'src/main/*'
gilot hotspot -i log-frontend.csv \
  --allow-files 'packages/*' \
  --ignore-files '*/__tests__/*' '*.spec.ts' '*.stories.js'

# 出力結果
------------------------------------------------------------
    gilot hotspot ( https://github.com/hirokidaichi/gilot )
------------------------------------------------------------

 hotspot  commits  authors file_name
   19.57       57        5 src/main/java/<path_to_file>
   17.71       67        5 src/main/java/<path_to_file>

また、 --csv -o 出力先ファイルパスCSV出力が可能です。この場合、除外していないすべてのファイルについて、解析結果が出力されます。

gilot hotspot -i log-backend.csv --csv -o hotspot-backend.csv \
  --allow-files 'src/main/*'
gilot hotspot -i log-frontend.csv --csv -o hotspot-frontend.csv \
  --allow-files 'packages/*' \
  --ignore-files '*/__tests__/*' '*.spec.ts' '*.stories.js'

以下のようなCSVが出力されました。--csv を付与せず実行した場合より、項目が増えています。

file_name,hotspot,commits,authors,edit_rate,lines
src/main/java/<path_to_file>,19.56934308077001,57,5,0.7472647702407003,1828
src/main/java/<path_to_file>,17.703904086980828,67,5,0.6457142857142857,1050

hotgraph

hotgraph コマンドにて、同時に変更されやすいファイルのネットワーク図を出力します。こちらも hotspot と同様、ユニットテスト関連ファイルなどを除外するため、--allow-files および --ignore-files を指定しております。

また、Windows環境では --stop-retry を付与しないとエラーが発生するため、オプションを付与しております。

gilot hotgraph -i log-backend.csv --stop-retry -o hotgraph-backend.png \
  --allow-files 'src/main/*'
gilot hotgraph -i log-frontend.csv --stop-retry -o hotgraph-frontend.png \
  --allow-files 'packages/*' \
  --ignore-files '*/__tests__/*' '*.spec.ts' '*.stories.js'

それぞれの結果は、以下のようになりました。(ファイル名が含まれるため、ぼかしをかけています)

f:id:h-yamamoto_holmescloud:20200904172917p:plain
hotgraph-backend

f:id:h-yamamoto_holmescloud:20200904172938p:plain
hotgraph-frontend

同時に変更されやすいファイルが、同じ色の円で表示されます。

author

author コマンドにて、コミットした人ごとのコミット数や、割合を出力します。

デフォルトではトップ10人が出力され、それ以外の人は「Others」としてまとめられます。人数は -t 人数 で指定可能です。

-n 名前 で、出力画像右上にタイトルを追加できます。

plot と同様、backendとfrontend、両方合わせたものの3種類を出力してみます。

gilot author -i log-*.csv -o author-all.png -n all
gilot author -i log-backend.csv -o author-backend.png -n backend
gilot author -i log-frontend.csv -o author-frontend.png -n frontend

それぞれの結果は、以下のようになりました。(個人名が含まれるため、私とOthers以外はぼかしをかけています)

f:id:h-yamamoto_holmescloud:20200904175731p:plain
author-all

f:id:h-yamamoto_holmescloud:20200904175757p:plain
author-backend

f:id:h-yamamoto_holmescloud:20200904175831p:plain
author-frontend

info

info コマンドにて、各コマンドの解析元となる情報を、JSONで出力できます。

plot と同様、 -t オプションを指定できますが、gilot v0.2.4 では無視されてデフォルトの2週間で出力されるようです。

-o オプションは指定できないため、ファイル出力する場合はリダイレクトします。

gilot info -i log-*.csv -t 1w > info-all.json
gilot info -i log-backend.csv -t 1w > info-backend.json
gilot info -i log-frontend.csv -t 1w > info-frontend.json

以下のJSONが出力されます。タイムスロットが2週間になっています。

{
    "gini": 0.23784183100679956,
    "output": {
        "lines": 236045,
        "added": 62121,
        "refactor": 0.7368256052871274
    },
    "since": "2020-06-02T09:01:06.000000000",
    "until": "2020-09-03T17:01:01.000000000",
    "timeslot": "2 Weeks",
    // 以下省略

振り返り

それぞれの解析処理を実行してみました。いずれの解析も、 gilot log でログ出力しておけば後から実行できるため、CIと連携させて出力するのは難しくないと思います。

以下所感です。

plot

スポットで出力するだけでは、あまりメリットを感じませんでした。

1か月などの期間で定期的に出力し、大きな変化があれば原因を特定、必要があれば改善するなど、実際のプロダクト開発に利用することで意味が出てくるのではないかと思います。

(根本的なところを言うと、それぞれのグラフの意味をきちんと把握していないので、まずはそこから学習しないとです...)

hotspot, hotgraph

これらはファイル間の関連性、依存性を確認することができるので、スポットでも有用だと思います。

静的解析ツールなどによる複雑性の確認と組み合わせることで、リファクタリングの優先度が高いファイルの発見に使えそうだと思いました。

author

これはそのままコミット数になります。

見ていると面白いですが、同じチーム内でも各人のコミット粒度が同じとなることはなかなかないと思うので、単体では何かの指標として使いにくいかと思います。

info

それぞれの解析結果の元となる情報のため、より高度な利用方法になるかと思います。

ツールの習熟が進み、いずれかの指標を画像ではなくデータとして扱いたいときに、こうしたコマンドがあるのはありがたいと思います。

期間指定変更後の再plot

frontendから抽出する期間を2020/6/15以降に変更して plot してみたところ、コミット行数の上振れが解消されました。

backendも合わせて期間を変更してみます。

gilot log --full -b master -o log-backend.csv --since 2020-06-15 --full backend/
gilot log --full -b master -o log-frontend.csv --since 2020-06-15 --full frontend/

gilot plot -i log-*.csv -t 1w -o plot-all.png
gilot plot -i log-backend.csv -t 1w -o plot-backend.png
gilot plot -i log-frontend.csv -t 1w -o plot-frontend.png

結果は以下のようになりました。コミット行数の上振れがなくなり、グラフの上限値が適切な値になっています。

f:id:h-yamamoto_holmescloud:20200904215634p:plain
plot-all(期間変更後)

f:id:h-yamamoto_holmescloud:20200904215711p:plain
plot-backend(期間変更後)

f:id:h-yamamoto_holmescloud:20200904204505p:plain
plot-frontend(期間変更後)

最後に

Holmesではエンジニア・デザイナーを募集しております。ご興味がある方はこちらからご連絡ください。

lab.holmescloud.com

lab.holmescloud.com