「サイボウズ・アドベントカレンダー」の5日目です(これまでの記事一覧)。第1週は無事クリアできました。ありがとうございます。
こんにちは、社内システム開発チームの山田です。
突然ですがサイボウズは何を販売している会社でしょう?
そうですグループウェア・・・のライセンスです!社内システム開発チームでは主にライセンスの販売管理システムを担当しています。
開発しているシステムは、cybozu.com を販売するための cybozu.com Store と cybozu.com Store と連携する社内向けのライセンス販売管理システムです。クラウド化が打ち出される前から社内向けシステムのリプレイスを進めていたのですが、結果としてビジネスの変化を予想したかのような絶妙なタイミングとなりました!
今回はそのリプレイスでうまくいった方針とちょっとしたTipsを紹介します。
リプレイスしたシステム、しないデータベース
リプレイスしたシステムは10年ほど前からあるライセンス販売管理システムで、2~3人でメンテナンスされてきました。開発言語は Delphi 5 で、クライアントアプリケーションです。これを C# ASP.NET の Web アプリケーションにリプレイスしています。
データベースには Oracle を使っていますが、リプレイスの目的や、社内の歴史的な事情からデータベースはそのまま利用することになりました。
臭い物に蓋をする
よくある状況ですがデータベースには、
- テーブルとカラムの名前がローマ字で読みづらい/長すぎる
- カラムの名前が統一されていない
- 使われていないフィールドが沢山ある
- 正規化されていない
などの問題ありました。
データベースをリプレイスしないとはいえ、リファクタリングぐらいはやってしまいたくなります。
しかし、リプレイスが完全に完了するまでは古いシステムと平行運用をします。古いシステムの理解度は浅いため変更による影響の調査が大変ですし、そもそも既存システムの方は変更したくありません。リファクタリングしたい気持ちをぐっと堪えて、リプレイスが完了するまでは、臭い物に蓋をするという方針でその時に備えることにしました。
蓋は O/R マッパー
データアクセスには O/R マッパーの .NET Entity Framework を使いました。Oracle のプロバイダは標準で用意されていないため、 dotConnect for Oracle を使っています。
O/R マッパーはオブジェクトとスキーマのミスマッチを埋めてくれるものですが、古いスキーマと理想のスキーマのミスマッチも埋めてくれます!
C# 側のクラスはスキーマと名前を合わせる必要がありません。クラスのフィールドの名前を変え、使われていないものを削れば、ソースコードからは古いスキーマが見えなくなって
- カラムとテーブルの名前がローマ字で読みづらい/長すぎる
- カラムの名前が統一されていない
- 使われていないフィールドが沢山ある
という問題が解消されます。ちなみにローマ字から英語への変換は、業務の専門用語などを無理に訳すと、状況が悪化して読んでも理解できなくなってしまったため、ほどほどにしておきました。
実は最初は名前を変えるという発想がなく、ツールでスキーマから生成したクラスをそのまま使っていました。書いてみると当然のことですが、ツールに振り回されてしまっていたのだと思います。
その作業と合わせて NOT NULL 制約や、UNIQUE 制約の追加などを行った結果、データベースは大分扱いやすいものになりました。
ツールの素敵な機能紹介
ついでに Entity Framework と Visual Studio の素敵な機能を紹介します。
Entity Framework の複合型
Entity Framework には複合型というテーブルのクラスとは別のクラスを定義できる機能があります。
たとえば、会社の住所情報は、会社や受注、見積情報など色々なテーブルに必要です。特別な理由がなければ正規化して住所テーブルを作ると思います。しかし、古いスキーマには住所テーブルはなく、住所情報は各テーブルにありました。
こんな時に役立つのが複合型です。住所のフィールドを抽出して住所クラスという複合型を作成できます。この住所クラスは各テーブルのクラスで共有できるため、正規化された住所テーブルと大体同じように扱えます。(全く同じではないので多少制限事項はあります)
これで、正規化されていない、という問題もソースコードから見ると多少は温和されます。
Visual Studio の補完
データベースのカラムの命名規則に、テーブル毎にテーブル名かそれの短縮形をプレフィックスにつけるというものがあります。 SALES_ITEM テーブルの NAME であれば SI をつけて SI_NAME といった感じです。
賛否両論あるように思いますが、カラム名が完全にユニークになるため、SQLを書くときにテーブルの別名を付けなくても良くなるというメリットがあります。
今思うとこのプレフィックスは、 C# のクラスのフィールドには不要なので取り除くべきでしたが付けたままにしていました。なぜ付けたままになったのかというと、コーディング時に気にならなかったためです。
Visual Studio 2010 からインテリセンスが強化されて候補の検索が先頭一致でなくなりました。たとえば、 SalesItem クラスの SiName と入力するために Name と打てば候補に出てきてくれます。 Si から打つ必要がないためスムーズに入力できます。
完全にバッドノウハウですが大好きな Visual Studio のちょっとした素敵な機能を紹介してみました。
SQL で実装されている複雑なビジネスロジックをなくす
O/R マッパーを使っても SQL を使うところは、なかなか無くせないものです。
リプレイス中のライセンス販売管理システムには、最新のライセンスを計算する、という機能があります。普通は最後に購入したライセンスが最新なので計算する必要なんてないように思えますが、ライセンスには種類があるためそれが出来ません。
たとえばライセンスにユーザー数とメールスペースというものがあるメールワイズを例にすると
- 新規ライセンス(5ユーザーで1メールスペース)
- ユーザー追加ライセンス(5ユーザーから10ユーザーに変更)
- メールスペース追加ライセンス(1スペースから3スペースに変更)
→ 最新のライセンスは10ユーザーで3スペース
というように、過去のライセンスを考慮する必要があります。このライセンスの種類というのが製品のバージョン毎に違うため、最新のライセンスの計算は結構複雑になります。
サイボウズでは MySQL の利用率が圧倒的に高いので Oracle を使っていると肩身の狭い思いをするのですが、そんな MySQL に無くて Oracle( PostgreSQL と SQL Server にも)にあるものが分析関数と WITH 句です。機能の説明は省きますが複雑なSQLを簡潔に書けるようになります。
ただそれゆえに、というわけでもないですが SQL でやるべきでないようなロジックまで SQL で実現されていました。個人的に SQL は好きで、複雑なロジックを実現する SQL を考えたり読んだりするのは、パズルを解くような楽しさがありますが、毎回パズルを解くのは大変です。
SQL を全く使わないようにするのは難しいですが、そういった複雑な SQL を残しておくと古いスキーマへの依存度が高いままになってしまいます。リプレイスの際に SQL から C# へロジックを移して、計算結果を保存しておくようにしました。
現在の状況
以上のような方針で1年ほど前から進めてきたリプレイスは、次のリリースで完了します。ここまでくるとわからない部分はほぼ無くなってきて、データベースのリファクタリングも可能になってきました。
今の問題は、愛着は生まれてきたものの、薄れつつあるスキーマへの執念をどうやって取り戻すかということに変わってきています。
次回は、来週月曜日となります。「usercustomize による Python カスタマイズ」をお送りします。お楽しみに。