オブジェクト指向で考えるデータ永続化
皆様こんにちは。i-Vinci の下位です。
ここ最近寒いなぁと思っていたら、東京では雪まで降ってしまいました。
私、雪があまり降らない地域出身なので、降雪までいくと少々テンションが上がります。
仕事休んで雪合戦したい
さて、今日のテーマです。
ビジネスアプリなら必ず遭遇するであろう「データ永続化」を題材に考えてみます。
データの永続化について考える
永続化について意味を調べてきました。
永続性とは、物事が長続きする性質やその度合い。IT 分野では、コンピュータプログラムの処理対象となるデータが、プログラムの終了やコンピュータの電源断後も消滅せずに存続する性質を指すことが多い。
ざっくりといえば、プログラム終了後も「データが消えないこと」を示します。
業務要件で出てくる「データの永続化」はプログラム終了後もデータが消えなければ OK ということです。
本日の例題
本日取り扱う仮想テーマです。
下記要件を満たすサービス処理について考えてみます。
1)汎用入力フォームを作りたい。
2)画面上に氏名、回答内容を設け、入力内容を永続化してほしい。
入力データは下記 JSON を想定するものとします。
// リクエストデータ
{
"name": "テスト 太郎",
"answer": "回答内容的ななにか"
}
サンプル実装 part 1
ということで、サンプルです。
サービスロジック以外は重要でないため、記載を省略します。
// リクエストデータのモデル
class Content {
public string Name {get; set;}
public string Answer {get; set;}
}
// データ永続化を担うサービスインターフェース
interface IContentService
{
/**
** 引数オブジェクトの永続化を行う
**/
void Register(Content content);
}
// データ永続化を担うサービスロジック
class ContentService : IContentService
{
public void Register(Content content)
{
// DB接続文字列を作成する処理
var connectionString = CreateConnectionString();
// DB接続取得
using (var connection = new SqlConnection(connectionString))
{
try
{
connection.Open();
// INSERT句の生成・実行
var command = new SqlCommand(null, connection);
connection.CommandText = "INSERT INTO Contents VALUES (@name, @answer)";
var nameParam = new SqlParameter("@name", SqlDbType.Varchar, 20);
var answerParam = new SqlParameter("@answer", SqlDbType.Varchar, 100);
nameParam.Value = content.Name;
answerParam.Value = content.Answer;
command.Parameters.Add(nameParam);
command.Parameters.Add(answerParam);
command.Prepare();
command.ExecuteNonQuery();
}
finally
{
connection.Close();
}
}
}
}
はい。DB を使った永続化を前提とするサービスロジックを記載しました。
ビジネスロジックと永続化ロジック、密ではないですか
上記、業務では比較的遭遇しやすいロジックになります。
パラメータに応じて SQL を構築、その後 RDBMS に問い合わせをするロジックです。
因みに、このロジックは下記の問題点を持っています。
- 保存先は SQL が利用可能なミドルウェアであることを前提とする(データ永続化先の固定)
- サービスロジックが SQL という、ビジネスに直接関係ない知識を有している (データ操作ロジックとの密結合)
1.の問題点
データ永続化先が変更となる場合の変更容易性が保たれていません。
限定的なケースですが、ポータブルモードの様な挙動を実現する際に障壁となりえます。
# 例:ポータブルモードのイメージ
アプリケーション設定または、サーバー疎通状況に応じて、データの保存先を可変とする方法。
* ケース1:任意のミドルウェア(DB等)
* ケース2:テキスト保存(JSON等)
2.の問題点
こちらはビジネス観点で考えた際の問題です。
ビジネス要求は「データの永続化」のみです。サービスロジックは「永続化の手続き」を知っていてはいけません。
「永続化の手続きを知る」ということは、そのロジックを内包することです。
つまり、サービスロジックと永続化ロジックの密結合に陥ってしまうためです。
無論、モジュール同士が密結合になれば、変更容易性は低下します。
将来への不安を拭うのであれば、サービスロジックとデータ操作を分離したいところです。
ではどうすればよいのか
前述の問題点ではサービスロジックが SQL という具体的な手段を明示した為、問題となることを示しました。
ではどうするか。
具体的な手段を明示しなければよいのです。データの保存処理には抽象化を適用しましょう。
抽象化を使った実装
では抽象化を使った実装です。
現実装では MAX1 件しか登録できないですが、抽象化の説明とは関連しない為、省略とします。
まずはデータ永続化ロジックを作成します。
共通の interface を定義し、それぞれの永続化ロジックに実装することが重要です。
interface IContentRepository
{
/**
* データ永続化を行うメソッド
**/
void Save(Content content);
}
/**
* DBへの永続化処理を担うロジック
**/
class DBContentRepository : IContentRepository
{
public void Save(Content content)
{
// DB接続文字列を作成する処理
var connectionString = CreateConnectionString();
// DB接続取得
using (var connection = new SqlConnection(connectionString))
{
try
{
connection.Open();
// INSERT句の生成・実行
var command = new SqlCommand(null, connection);
connection.CommandText = "INSERT INTO Contents VALUES (@name, @answer)";
var nameParam = new SqlParameter("@name", SqlDbType.Varchar, 20);
var answerParam = new SqlParameter("@answer", SqlDbType.Varchar, 100);
nameParam.Value = content.Name;
answerParam.Value = content.Answer;
command.Parameters.Add(nameParam);
command.Parameters.Add(answerParam);
command.Prepare();
command.ExecuteNonQuery();
}
finally
{
connection.Close();
}
}
}
}
/**
* ローカルリソースへの永続化を担うロジック
* 当記事ではJSONファイルへの保存を想定。
**/
class LocalContentRepository : IContentRepository
{
private const string RESOURCE_PATH = "/app/store/resource.json";
public void Save(Content content)
{
string json = JsonSerializer.Serialize(content);
File.WriteAllText(RESOURCE_PATH, json);
}
}
では、作成した永続化ロジックをサービスロジックに組み込みます。
// データ永続化を担うサービスロジック
class ContentService : IContentService
{
public void Register(Content content)
{
// 永続化サービスインスタンスを生成。
// 引数は仮置きとしているが、アプリケーション特性に応じて外部入力を受け入れられるようにすると良い。(App.Settings等)
IContentRepository repository = CreateRepository("file");
// 永続化を実行。
repository.Save();
}
/**
* 引数に応じて永続化サービスインスタンスを生成する
**/
private IContentRepository CreateRepository(string type)
{
return type == "file"
? new LocalContentRepository()
: new DBContentRepository();
}
}
はい。実装完了です。
無事、サービスロジックから永続化の手続きを除去できました。
ついでにサービスロジックの複雑度も低減させることに成功しています。一石二鳥ですね。
まとめ
いかがでしたでしょうか。
今回はデータ永続化処理を例に上げて、オブジェクト指向を利用した改善案を提示させていただきました。
今回も記事を執筆して感じましたが、抽象化は非常に強力なツールです。
やり過ぎは危険ですが、適切に利用することでモジュールの安定性を一歩底上げしてくれる機能と強く感じます。
皆様も是非、抽象化などの強力機能を駆使してコーディングに望んでみてください。
簡潔なプログラムは作る側、見る側双方を幸せにしてくれる...筈です!
最後に、今回採用したものは「リポジトリパターン」と呼ばれる、永続化処理の隠蔽を担うデザインパターンになります。
他にも様々なデザインパターンが存在し、どれもモジュール簡潔化に一役買ってくれる素敵な手法になります。
興味のある方は是非、調べてみてください。