SAP CAP上でDI/DIコンテナをサクッと実装: 理論編

はじめに

本記事は、第一章の理論編と第二章のハンズオン編に分けて、CAP上でDI(依存性の注入)およびDIコンテナ(IoCコンテナ)を活用する方法を説明します。Side-by-Sideアプリケーション開発において、テスト容易性、保守性、モジュール性を向上させるアーキテクチャ設計の参考になれば幸いです。

注意点:本記事ではDI/DIコンテナの利点を述べますが、コード量の増加、構造の複雑化、パフォーマンスのオーバーヘッドなどのデメリットもあるため、要件に合わせて取捨選択してください。

対象読者

本記事はCAPの基礎知識がある前提で進めますので、CAPの基礎を学びたい方はSAP CAP Cookbookを参考にしてください。DI/DIコンテナを初めて使用する方、CAPプロジェクトでの活用方法を知りたい方、実際に手を動かして試してみたい方向けの内容となります。

シンプルなアプリ設計の問題点

シンプルなレイヤードアーキテクチャを意識したCAPアプリの開発では、一般的に次の実装にします。

レポジトリの作成。ハンドラでビジネスロジックを記述。cdsファイルでサービスを定義し、それをもとにサービスクラスの詳細を記述。

この場合、ハンドラ内でレポジトリクラスをインスタンス化する、つまりハンドラがレポジトリクラスに依存する形となります。

Bookデータを全て取得するコードを例に上記のイメージを掴みましょう。

import { Book } from “./book”;

class BookRepository {
async findAllBooks(): Promise<Book[]> {
return await SELECT.from(“Books”);
}
}

export { BookRepository };

たしかに、readBooksHandlerがBookRepositoryという具象クラスに依存しています。

import { BookRepository } from “./bookRepository”;

export const readBooksHandler = () => async () => {
// readBooksHandler内でBookRepositoryをインスタンス化している。
const bookRepository = new BookRepository();
const allBooks = await bookRepository.findAllBooks();
return allBooks;
};

モックへ置き換えたい場合は、手動でBookRepositoryをメンテナンスしなければいけません。

DI(Dependency Injection)とは?

上記の問題を解決するために、DI(Dependency Injection)というオブジェクトの依存関係を外部から注入するデザインパターンがあり、日本語では「依存性の注入」と呼ばれます。

先ほどはreadBooksHandlerがBookRepositoryという具象クラスに依存しましたが、IBookRepositoryという抽象型に依存する形に変更します。これにより、IBookRepositoryを実装した具象クラスであれば、BookRepositoryクラスでもMockBookRepositoryクラスでも利用できるようになります。その結果、テスト容易性が向上します。実装方法を見てみましょう。

まずはIBookRepositoryを定義します。

import { Book } from “./book”;

export interface IBookRepository {
findAllBooks(): Promise<Book[]>;
}

BookRepositoryはIBookRepositoryを実装したクラスに書き換えます。

import { IBookRepository } from “./iBookRepository”;
import { Book } from “./book”;

class BookRepository implements IBookRepository {
async findAllBooks(): Promise<Book[]> {
return await SELECT.from(“Books”);
}
}

export { BookRepository };

IBookRepositoryを型に持つbookRepositoryを引数にとります。

import { IBookRepository } from “./iBookRepository”;

export const readBooksHandler =
(bookRepository: IBookRepository) =>
async () => {
const books = await bookRepository.findAllBooks();
return books;
};

CatalogService内でBookRepositoryをインスタンス化します。これにより、モックを使用したい場合にはMockBookRepositoryへ書き換えるだけでよくなりました。

import { BookRepository } from “./bookRepository copy”;
import { readBooksHandler } from “./readBooksHandler”;
import cds from “@sap/cds”;

class CatalogService extends cds.ApplicationService {
async init() {
const bookRepository = new BookRepository();

// モックを使用したい時は、下記のbookRepositoryを使う。
// const bookRepository = new MockBookRepository();

this.on(“READ”, “Books”, readBooksHandler(bookRepository));
}
}

export default CatalogService;

補足:今回はsubmitOrderHandlerを関数として定義していますが、クラスとして定義する場合には、コンストラクタの引数としてIBookRepositoryを抽象型として持つオブジェクトを受け取ることで、DIを実現できます。

DIコンテナの必要性

DIを使うことでハンドラとレポジトリが疎結合となり、モックへの差し替えが容易になりました。しかし、ハンドラのインスタンス化や依存関係の解決をハンドラの呼び出し先であるサービスクラスが担う必要が出てきました。これは、各レイヤー自体あるいはレイヤー間の依存関係が複雑になった際に管理が煩雑になることを意味し、エラーの原因となり得ます。そこで利用するのがDIコンテナです。依存オブジェクトのインスタンス化と依存関係の解決に関するコードをDIコンテナの設定ファイルに集約でき、差し替えも即座にできるようになります。詳細はハンズオン編をご覧ください。

まとめ

本記事では、シンプルなアプリ設計の問題点と、それを解決するためのDI/DIコンテナの有用性について説明しました。次は、「SAP CAP上でDI/DIコンテナをサクッと実装: Node.js ハンズオン編」を参照しながら、実際にコードを書いてみてください。

 

​ はじめに本記事は、第一章の理論編と第二章のハンズオン編に分けて、CAP上でDI(依存性の注入)およびDIコンテナ(IoCコンテナ)を活用する方法を説明します。Side-by-Sideアプリケーション開発において、テスト容易性、保守性、モジュール性を向上させるアーキテクチャ設計の参考になれば幸いです。注意点:本記事ではDI/DIコンテナの利点を述べますが、コード量の増加、構造の複雑化、パフォーマンスのオーバーヘッドなどのデメリットもあるため、要件に合わせて取捨選択してください。対象読者本記事はCAPの基礎知識がある前提で進めますので、CAPの基礎を学びたい方はSAP CAP Cookbookを参考にしてください。DI/DIコンテナを初めて使用する方、CAPプロジェクトでの活用方法を知りたい方、実際に手を動かして試してみたい方向けの内容となります。シンプルなアプリ設計の問題点シンプルなレイヤードアーキテクチャを意識したCAPアプリの開発では、一般的に次の実装にします。レポジトリの作成。ハンドラでビジネスロジックを記述。cdsファイルでサービスを定義し、それをもとにサービスクラスの詳細を記述。この場合、ハンドラ内でレポジトリクラスをインスタンス化する、つまりハンドラがレポジトリクラスに依存する形となります。Bookデータを全て取得するコードを例に上記のイメージを掴みましょう。import { Book } from “./book”;

class BookRepository {
async findAllBooks(): Promise<Book[]> {
return await SELECT.from(“Books”);
}
}

export { BookRepository };たしかに、readBooksHandlerがBookRepositoryという具象クラスに依存しています。import { BookRepository } from “./bookRepository”;

export const readBooksHandler = () => async () => {
// readBooksHandler内でBookRepositoryをインスタンス化している。
const bookRepository = new BookRepository();
const allBooks = await bookRepository.findAllBooks();
return allBooks;
};モックへ置き換えたい場合は、手動でBookRepositoryをメンテナンスしなければいけません。DI(Dependency Injection)とは?上記の問題を解決するために、DI(Dependency Injection)というオブジェクトの依存関係を外部から注入するデザインパターンがあり、日本語では「依存性の注入」と呼ばれます。先ほどはreadBooksHandlerがBookRepositoryという具象クラスに依存しましたが、IBookRepositoryという抽象型に依存する形に変更します。これにより、IBookRepositoryを実装した具象クラスであれば、BookRepositoryクラスでもMockBookRepositoryクラスでも利用できるようになります。その結果、テスト容易性が向上します。実装方法を見てみましょう。まずはIBookRepositoryを定義します。import { Book } from “./book”;

export interface IBookRepository {
findAllBooks(): Promise<Book[]>;
}BookRepositoryはIBookRepositoryを実装したクラスに書き換えます。import { IBookRepository } from “./iBookRepository”;
import { Book } from “./book”;

class BookRepository implements IBookRepository {
async findAllBooks(): Promise<Book[]> {
return await SELECT.from(“Books”);
}
}

export { BookRepository };IBookRepositoryを型に持つbookRepositoryを引数にとります。import { IBookRepository } from “./iBookRepository”;

export const readBooksHandler =
(bookRepository: IBookRepository) =>
async () => {
const books = await bookRepository.findAllBooks();
return books;
};CatalogService内でBookRepositoryをインスタンス化します。これにより、モックを使用したい場合にはMockBookRepositoryへ書き換えるだけでよくなりました。import { BookRepository } from “./bookRepository copy”;
import { readBooksHandler } from “./readBooksHandler”;
import cds from “@sap/cds”;

class CatalogService extends cds.ApplicationService {
async init() {
const bookRepository = new BookRepository();

// モックを使用したい時は、下記のbookRepositoryを使う。
// const bookRepository = new MockBookRepository();

this.on(“READ”, “Books”, readBooksHandler(bookRepository));
}
}

export default CatalogService;補足:今回はsubmitOrderHandlerを関数として定義していますが、クラスとして定義する場合には、コンストラクタの引数としてIBookRepositoryを抽象型として持つオブジェクトを受け取ることで、DIを実現できます。DIコンテナの必要性DIを使うことでハンドラとレポジトリが疎結合となり、モックへの差し替えが容易になりました。しかし、ハンドラのインスタンス化や依存関係の解決をハンドラの呼び出し先であるサービスクラスが担う必要が出てきました。これは、各レイヤー自体あるいはレイヤー間の依存関係が複雑になった際に管理が煩雑になることを意味し、エラーの原因となり得ます。そこで利用するのがDIコンテナです。依存オブジェクトのインスタンス化と依存関係の解決に関するコードをDIコンテナの設定ファイルに集約でき、差し替えも即座にできるようになります。詳細はハンズオン編をご覧ください。まとめ本記事では、シンプルなアプリ設計の問題点と、それを解決するためのDI/DIコンテナの有用性について説明しました。次は、「SAP CAP上でDI/DIコンテナをサクッと実装: Node.js ハンズオン編」を参照しながら、実際にコードを書いてみてください。   Read More Technology Blogs by SAP articles 

#SAP

#SAPTechnologyblog

You May Also Like

More From Author