設計/コンポーネント設計(3)
2023年4⽉14⽇
浅海智晴
クラウドアプリケーションのための
オブジェクト指向分析設計講座
第22回
作業分野
SimpleModeling2021
• オブジェクト指向分析設計での共通範囲
• UML/UP
• 本講座で使⽤するUMLプロファイル
• プロファイル:SimpleModeling2021 (SM2021)
• オブジェクト指向分析設計の基本からの拡張部を明確化
• アジャイル開発
• Communication
• Embrace Change
• Travel Light
• Scaling
• Component-Based Development
• クラウド・アプリケーション
• モデル駆動開発
SM2021
Travel Light
Embrace Change
Cloud
Model-Driven
Scaling
CBD
第1部 基本編の構成
• 概論 [第1回]
• 開発プロセス [第2回]
• 基本モデル [第3回]
• 静的モデル(1) [第4回]
• 静的モデル(2) [第5回]
• 動的モデル [第6回]
• 協調モデル [第7回]
• 関数モデル [第8回]
• 物理モデル [第9回]
• 作業分野 [第10回]
• ビジネス・モデリング [第11回]
• 要求 [第12回]
• 要求/ユースケース [第13回]
• 要求/シナリオ [第14回]
• 分析 [第15回]
• 分析/コンポーネント分析 [第16回]
• 分析/イベント駆動 [第17回]
• 作業分野
• 設計 [第18回]
• 設計/アーキテクチャ設計 [第19回]
• 設計/コンポーネント設計(1) [第20回]
• 設計/コンポーネント設計(2) [第21回]
• 設計/コンポーネント設計(3) [第22回]
• 設計/ドメイン設計 [第23回]
• 設計/ UX/UI設計 [第24回]
• 実装 [第25回]
• テスト [第26回]
• アプリケーション・アーキテクチャ [第27回]
• ドメイン・モデル [第28回]
• アプリケーション・モデル [第29回]
• プレゼンテーション・モデル [第30回]
• ケーススタディ[第31回]
• 要求モデル [第32回]
• 分析モデル [第33回]
• 設計モデル [第34回]
• 実装 [第35回]
• テスト [第36回]
本講座のアプローチ
• オブジェクト指向分析設計の基本を確認
• UML + UP(Unified Process)
• CBD (Component-Based Development)
• 最新技術でアップデート
• クラウド・コンピューティング
• イベント駆動、分散・並列
• ビッグデータ、AI、IoT
• コンテナ
• 関数型
• OFP(Object-Functional Programming), Reactive Streams
• ルール, AI
• DevOps
• アジャイル開発
• DX (Digital Transformation)
第25回 アプリケーション・アーキテクチャ
第2回 開発プロセス
第9回 物理モデル
第11回 ビジネス・モデリング
第2部 クラウド・アプリケーション編
第21回 設計/ドメイン設計
第20回 設計/コンポーネント設計
第2部 クラウド・アプリケーション編
本講座の選択
• プログラミング⾔語
• Scala
• Javaエコシステム
• Pythonを併⽤
• コンテナ
• Docker/Kubernetes
• CI/CD
• Daggar
• ミドルウェア
• AWS
• クラウド・プラットフォーム
• AWS
• アプリケーション・フレームワーク
• 本講座が定義する仮想的なアプリケーション・フレームワーク
• コンポーネント・フレームワーク
• クラウド・アプリケーション
• 関数型
原理 (Principle)
• Agile Software Development [ASD]
• SRP (The Single Responsibility Principle)
• OCP (The Open-Close Principle)
• LSP (The Liskov Substitution Principle)
• …
• GRASP (General Responsibility Assignment Software Patterns or Principals)
• Low Coupling
• High Cohesion
• …
• Writing Effective Use Cases [WEUC]
• Scope
• …
パターン (Pattern)
• Design Patterns [DP]
• Observer, Strategy, …
• Domain Driven Design [DDD]
• Ubiquitous Language, Intention-
Revealing Interfaces, …
• Analysis Patterns [AP]
• Party, Quantity, …
• Pattern-Oriented Software
Architecture [POSA]
• Layers, Pipes and Filters, …
• Patterns of Enterprise
Application Architecture [PEAA]
• Unit of Work, Data Transfer Object,
…
• Enterprise Integration Patterns
[EIP]
• Message Bus, Aggregator, …
• Patterns for Effective Use
Cases [PEUC]
• CompleteSingleGoal,
VerbPhraseName, …
• AntiPatterns [AnP]
• Stovepipe System, Analysis
Paralysis, …
内容
• コンポーネント
• コンポーネント設計へのアプローチ
• コンポーネント仕様設計
• コンポーネント実現設計
コンポーネント実現設計
コンポーネント実現設計
• コンポーネント仕様を実現(realization)するための設計を⾏う
• コンポーネント仕様をプログラミング⾔語+実⾏基盤上でどの
ように実現するのかが重要
• コンポーネント仕様設計時もこの前提で仕様を設計していく
• コンポーネントの内部構造はモデリングによる設計は⾏わず、
直接プログラミングする⽅が効率的
• 複雑な構造の場合はモデリングしてもよい
• コンポーネント仕様の設計もUMLによるモデリングは概要に留
め、詳細はプログラミング⾔語で⾏うのが効率的
• プログラミング⾔語の仕様にないモデルの取り扱いには⼯夫が必要
Travel Light
Travel Light
プログラミング⾔語による仕様記述
• クラス、インタフェース、メソッドなどUMLと相互運⽤性のあるモ
デルはそのまま活⽤
• 仕様をできるだけ型に落とし込む
• 専⽤データ型を定義
• 分岐の可能性をOptionやEitherなどで記述
• Monoidなど代数的構造を活⽤
• 制約をassertなどで実装
• アノテーションを活⽤
• 外部ツールと連動
• 振る舞いの仕様(シナリオ、状態機械)などはテスト・セットで実現
• プログラミング⾔語で記述が難しいものはUMLで補完
• 状態機械、アクティビティ
第8回 関数モデル
ライブラリ
• Google Guice
• DI(Dependency Injection)コンテナ
• Typesafe Config
• 設定ファイル読み込み
• Spire
• 関数型数値計算ライブラリ
インタフェース
インタフェース
• インタフェースはコンポーネント仕様で作成したモデルをその
ままプログラミング⾔語に落とし込めばよい
• プログラミング⾔語に落とし込める精度で設計を⾏う必要がある
• DTO(Data Transfer Object)の実現では以下の点を考慮する
• ADT(Algebraic Data Type), 制約, RPC, Monoid
• DbC(Design by Contract)はプログラミング⾔語のサポートが
ないので実現⽅法を⼯夫する必要がある
package reservation
import scala.util.Try
import componentframework.ExecutionContext
trait IReservationService {
def reserve(cmd: ReserveCommand)
(using ctx: ExecutionContext): Try[ReserveResult]
def unreserve(cmd: UnreserveCommand)
(using ctx: ExecutionContext): Try[ReserveResult]
def getReservation(id: ReservationId)
(using ctx: ExecutionContext): Try[Option[Reservation]]
def queryReservation(q: ReservationQuery)
(using ctx: ExecutionContext): Try[Vector[Reservation]]
}
IReservationService.scala
package reservation
import scala.util.Try
import componentframework.ExecutionContext
trait IReservationManagement {
// ...
}
package reservation
trait IReservationMetrix {
def numberOfCacheSlots: Int
def numberOfCacheHit: Int
}
package reservation
import scala.util.Try
import componentframework.ExecutionContext
trait IReservationAdmin {
// ...
}
IReservationManagement.scala IReservationAdmin.scala
IReservationMetrix.scala Serviceability
Serviceability
package reservation
import java.time.LocalDateTime
import spire.math.Interval
case class ReservationQuery(
resourceId: Option[ResourceId],
userId: Option[UserId],
interval:
Option[Interval[LocalDateTime]]
) {
}
case class ReserveCommand(
resourceId: ResourceId,
userId: UserId,
interval: Interval[LocalDateTime]
) {
}
case class UnreserveCommand(
id: ReservationId
) {
}
case class ReserveResult(
) {
}
case class UnreserveResult(
) {
}
Command.scala
package reservation
case class ReservationId(id: String) extends AnyVal {
}
case class ResourceId(id: String) extends AnyVal {
}
case class UserId(id: String) extends AnyVal {
}
DataType.scala
package reservation
import java.time.LocalDateTime
import spire.math.Interval
case class Reservation(
id: ReservationId,
resourceId: ResourceId,
userId: UserId,
interval: Interval[LocalDateTime]
) {
}
Reservation.scala
package reservation
…
class ReservationService @Inject()(
val rule: ReservationService.ReservationRule,
val datastore: IDataStore
) extends IReservationService, IReservationManagement, IReservationAdmin, IReservationMetrix, EventListener {
// IReservationService
def reserve(cmd: ReserveCommand)(using ctx: ExecutionContext): Try[ReserveResult] = ???
def unreserve(cmd: UnreserveCommand)(using ctx: ExecutionContext): Try[ReserveResult] = ???
def getReservation(id: ReservationId)(using ctx: ExecutionContext): Try[Option[Reservation]] = ???
def queryReservation(q: ReservationQuery)(using ctx: ExecutionContext): Try[Vector[Reservation]] = ???
// IReservationManagement
// IReservationAdmin
// IReservationMetrix
def numberOfCacheSlots: Int = ???
def numberOfCacheHits: Int = ???
ReservationService.scala (1/2)
// EventListener
def receive(evt: Event): Unit = Option(evt) collect {
case m: ReservedEvent => reserved(m)
case m: UnreservedEvent => unreserved(m)
}
def reserved(evt: ReservedEvent): Unit = ???
def unreserved(evt: UnreservedEvent): Unit = ???
}
object ReservationService {
case class ReservationRule(
)
object ReservationRule {
def create(p: Config): ReservationRule = ???
}
}
ReservationService.scala (2/2)
package reservation.datastore
trait IDataStore {
}
package reservation.datastore
class DataStore() extends IDataStore {
}
IDataSore.scala
DataSore.scala
DbC、制約
• DbCや制約を⽤いて仕様の精度を上げる
• コンパイル時にエラーを検出できないが、実⾏時にエラー検出ができ
る
• エラー検出と紐付いた仕様記述により仕様を明記
trait IReservationService {
protected def invariants: Try[Unit]
def reserve(cmd: ReserveCommand)
(using ctx: ExecutionContext): Try[ReserveResult] =
for {
_ <- invariants
_ <- reserve_precondition(cmd)
r <- do_reserve(cmd)
_ <- reserve_postcondition(cmd, r)
_ <- invariants
} yield r
protected def reserve_precondition(cmd: ReserveCommand)
(using ctx: ExecutionContext): Try[Unit]
protected def reserve_postcondition(
cmd: ReserveCommand, result: ReserveResult
)(using ctx: ExecutionContext): Try[Unit]
protected def do_reserve(cmd: ReserveCommand)(using ctx: ExecutionContext):
Try[ReserveResult]
}
IReservationService.scala
DbCの実現例:インタフェース側に実装
第8回 関数モデル
case class ReservationId(id: String) {
require (ReservationId.validate(id), s"Bad id: $id")
}
object ReservationId {
def validate(id: String): Boolean = ???
def create(id: String): Try[ReservationId] =
if (validate(id))
Success(ReservationId(id))
else
Failure(new IllegalArgumentException(s"Bad id: $id"))
}
制約を⽤いてデータタイプのバリデーション
ReservationId.scala
case class ReservationQuery(
resourceId: Option[ResourceId],
userId: Option[UserId],
interval: Option[Interval[LocalDateTime]]
) {
require (ReservationQuery.validate(resourceId, userId, interval), s"Bad query: $this")
}
object ReservationQuery {
def validate(
resourceId: Option[ResourceId],
userId: Option[UserId],
interval: Option[Interval[LocalDateTime]]
): Boolean = ???
def create(
resourceId: Option[ResourceId],
userId: Option[UserId],
interval: Option[Interval[LocalDateTime]]
): Try[ReservationQuery] =
if (validate(resourceId, userId, interval))
Success(ReservationQuery(resourceId, userId, interval))
else
Failure(new IllegalArgumentException(s"Bad query: $resourceId, $userId, $interval"))
}
制約を⽤いてDTOのバリデーション
ReservationQuery.scala
レセプション
レセプション
• UMLではイベントの受信エントリはレセプションとして記述
• プログラミング⾔語ではイベント受け取り処理として記述
• サービス・バスからコンポーネントまでのイベントの流通経路
はコンポーネント・フレームワークに依存
システムアーキテクチャ
Cloud
再掲 第6回 動的モデル
package componentframework.event
trait EventListener {
def receive(p: Event): Unit
}
package componentframework.event
trait Event {
}
Event.scala
EventListener.scala
// EventListener
def receive(evt: Event): Unit = Option(evt) collect {
case m: ReservedEvent => reserved(m)
case m: UnreservedEvent => unreserved(m)
}
def reserved(evt: ReservedEvent): Unit = ???
def unreserved(evt: UnreservedEvent): Unit = ???
}
object ReservationService {
case class ReservationRule(
)
object ReservationRule {
def create(p: Config): ReservationRule = ???
}
}
ReservationService.scala (2/2)
package reservation
import componentframework.event.Event
trait ReservationEvent extends Event {
}
case class ReservedEvent(
id: ReservationId
) extends ReservationEvent {
}
case class UnreservedEvent(
id: ReservationId
) extends ReservationEvent {
}
Event.scala
⽣成
⽣成
• UMLによる仕様モデルでは明記されないことが多い
• コンポーネントの⽣成⽅法は実⾏基盤となるコンポーネント・フレー
ムワークに依存する部分が⼤きい
• ⽣成の種類を選択
• コンストラクタ
• ファクトリ
• ビルダー
• DI (Dependency Injection)
package reservation
…
class ReservationService @Inject()(
val rule: ReservationService.ReservationRule,
val datastore: IDataStore
) extends IReservationService, IReservationManagement, IReservationAdmin, IReservationMetrix,
EventListener {
// IReservationService
def reserve(cmd: ReserveCommand)
(using ctx: ExecutionContext): Try[ReserveResult] = ???
def unreserve(cmd: UnreserveCommand)
(using ctx: ExecutionContext): Try[ReserveResult] = ???
def getReservation(id: ReservationId)
(using ctx: ExecutionContext): Try[Option[Reservation]] = ???
def queryReservation(q: ReservationQuery)
(using ctx: ExecutionContext): Try[Vector[Reservation]] = ???
// IReservationManagement
// IReservationAdmin
// IReservationMetrix
def numberOfCacheSlots: Int = ???
def numberOfCacheHits: Int = ???
ReservationService.scala (1/2)
package reservation
import javax.inject.Singleton
import com.google.inject.AbstractModule
import com.typesafe.config.Config
import reservation.ReservationService.ReservationRule
import reservation.datastore.IDataStore
import reservation.datastore.DataStore
class ReservationServiceModule(config: Config) extends AbstractModule {
override def configure(): Unit = {
bind(classOf[IDataStore]).to(classOf[DataStore]).in(classOf[Singleton])
bind(classOf[ReservationRule]).toInstance(ReservationRule.create(config))
}
}
ReservationServiceModule.scala
拡張点 (extension point)
拡張点
• 拡張点を要求インタフェース(Required Interface)として定義
し、DIなどのメカニズムで設定する
• 定義ファイルの情報を元にコンポーネント内で設定する⽅法もある
• コンポーネント・フレームワークの機能を使⽤するのがよい
• 部品化、テスト容易性のためにはできるだけ疎結合な設定⽅式
が望ましい Testability
package reservation
…
class ReservationService @Inject()(
val rule: ReservationService.ReservationRule,
val datastore: IDataStore
) extends IReservationService, IReservationManagement, IReservationAdmin, IReservationMetrix, EventListener {
// IReservationService
def reserve(cmd: ReserveCommand)(using ctx: ExecutionContext): Try[ReserveResult] = ???
def unreserve(cmd: UnreserveCommand)(using ctx: ExecutionContext): Try[ReserveResult] = ???
def getReservation(id: ReservationId)(using ctx: ExecutionContext): Try[Option[Reservation]] = ???
def queryReservation(q: ReservationQuery)(using ctx: ExecutionContext): Try[Vector[Reservation]] = ???
// IReservationManagement
// IReservationAdmin
// IReservationMetrix
def numberOfCacheSlots: Int = ???
def numberOfCacheHists: Int = ???
package reservation
import javax.inject.Singleton
import com.google.inject.AbstractModule
import com.typesafe.config.Config
import reservation.ReservationService.ReservationRule
import reservation.datastore.IDataStore
import reservation.datastore.DataStore
class ReservationServiceModule(config: Config) extends AbstractModule {
override def configure(): Unit = {
bind(classOf[IDataStore]).to(classOf[DataStore]).in(classOf[Singleton])
bind(classOf[ReservationRule]).toInstance(ReservationRule.create(config))
}
}
ReservationServiceModule.scala
変化点 (variation point)
変化点 (variation point)
• 外部ファイルや環境変数による定義を変化点に設定する⽅式が
ポイント
• コンポーネント・フレームワークの機能を使⽤するのがよい
• 今回は変化点を集めたルールオブジェクトを作成し、DIで設定
する⽅式で実現
• 部品化、テスト容易性のためにはできるだけ疎結合な設定⽅式
が望ましい Testability
package reservation
…
class ReservationService @Inject()(
val rule: ReservationService.ReservationRule,
val datastore: IDataStore
) extends IReservationService, IReservationManagement, IReservationAdmin, IReservationMetrix, EventListener {
// IReservationService
def reserve(cmd: ReserveCommand)(using ctx: ExecutionContext): Try[ReserveResult] = ???
def unreserve(cmd: UnreserveCommand)(using ctx: ExecutionContext): Try[ReserveResult] = ???
def getReservation(id: ReservationId)(using ctx: ExecutionContext): Try[Option[Reservation]] = ???
def queryReservation(q: ReservationQuery)(using ctx: ExecutionContext): Try[Vector[Reservation]] = ???
// IReservationManagement
// IReservationAdmin
// IReservationMetrix
def numberOfCacheHit: Int = ???
def numberOfCacheMiss: Int = ???
def numberOfOperationCalls: Int = ???
package reservation
import javax.inject.Singleton
import com.google.inject.AbstractModule
import com.typesafe.config.Config
import reservation.ReservationService.ReservationRule
import reservation.datastore.IDataStore
import reservation.datastore.DataStore
class ReservationServiceModule(config: Config) extends AbstractModule {
override def configure(): Unit = {
bind(classOf[IDataStore]).to(classOf[DataStore]).in(classOf[Singleton])
bind(classOf[ReservationRule]).toInstance(ReservationRule.create(config))
}
}
ReservationServiceModule.scala
メトリクス
メトリクス
• コンポーネント仕様設計では、変数を定義するのみだったが、
ここではメトリクス⽤のインタフェースを作成して、外部から
アクセスされることを明確にした
• コンポーネント・フレームワークの機能で集計されることを想
定
• Scala/Javaの場合はJMX(Java Management Extensions)に登録
する⽅式が有⼒
package reservation
…
class ReservationService @Inject()(
val rule: ReservationService.ReservationRule,
val datastore: IDataStore
) extends IReservationService, IReservationManagement, IReservationAdmin, IReservationMetrix, EventListener {
// IReservationService
def reserve(cmd: ReserveCommand)(using ctx: ExecutionContext): Try[ReserveResult] = ???
def unreserve(cmd: UnreserveCommand)(using ctx: ExecutionContext): Try[ReserveResult] = ???
def getReservation(id: ReservationId)(using ctx: ExecutionContext): Try[Option[Reservation]] = ???
def queryReservation(q: ReservationQuery)(using ctx: ExecutionContext): Try[Vector[Reservation]] = ???
// IReservationManagement
// IReservationAdmin
// IReservationMetrix
def numberOfCacheSlots: Int = ???
def numberOfCacheHists: Int = ???
package reservation
trait IReservationMetrix {
def numberOfCacheSlots: Int
def numberOfCacheHit: Int
}
IReservationMetrix.scala
ReservationService.scala (1/2)
テスト・ケース
テスト・ケース
• コンポーネント仕様記述の⼀環でテスト・ケースを作成しても
よい
• 仕様記述が難しい状態機械
• ユースケースから派⽣したシナリオ・テスト
コンポーネント・
フレームワーク
コンポーネント・フレームワーク
• 本講座⽤の仮想的なコンポーネント・フレームワークを⽤いる
• 今回は以下の機能を使⽤
• 実⾏コンテキスト
• イベント
package componentframework
class ExecutionContext {
}
package componentframework.event
trait Event {
}
package componentframework.event
trait EventListener {
def receive(p: Event): Unit
}
Execution.scala
Event.scala
EventListener.scala
物理モデル
物理モデル
• 物理モデルとして以下の成果物を実現する
• 配備モジュール
• モジュール(コンポーネント)を配備⽤にファイル化したJARファイル
• 定義ファイル
• ドキュメント
• 仕様書
• マニュアル
• リファレンス・マニュアル
• ユーザー・マニュアル
• テスト・ケース
テスト・ケース
テスト・ケース
• 作成したテスト・ケースはプログラム本体と同じソース・ツ
リーで管理する
• Scala/Javaのコンベンションに従う
• 必要なタイミングでいつでもテストできるような状態を維持す
る
$ sbt test
ドキュメント
ドキュメント
• 仕様書
• UMLモデル+補⾜記事で仕様書を作成
• リファレンス・マニュアル
• プログラムから⾃動⽣成 (Scaladoc/Javadoc)
• ユーザー・マニュアル
• ユースケースのシナリオを実際のプログラムで実現できるための利⽤
者とシステムの対話をドキュメント化
• ドキュメントは散逸しやすいので、ソースコードと同じバー
ジョン管理の配下で管理するとよい
$ sbt doc
まとめ
• コンポーネント仕様を実現(realization)するための設計を⾏う
• コンポーネント仕様をプログラミング⾔語+実⾏基盤上でどのように
実現するのかが重要
• コンポーネントの内部構造はモデリングによる設計は⾏わず、直接プ
ログラミングする⽅が効率的
• コンポーネント仕様の設計もUMLによるモデリングは概要に留め、詳
細はプログラミング⾔語で⾏うのが効率的
• 物理モデル
• 配備モジュール
• ドキュメント
• テスト・ケース
参考⽂献
• The Unified Modeling Language Reference
Manual, 2nd (Rumbaugh他, 2004)
• The Unified Modeling Language User Guide,
2nd (Booch他, 2004)
• The Unified Software Development Process
(Jacobson他, 1999)
• The Object Constraint Language, 2nd (Warmer
他, 2003)
• UML 2 and the Unified Process: Practical
Object-Oriented Analysis and Design (Arlow
他, 2005)
• OMG Unified Modeling Language Version 2.5
(OMG, 2015)
• 上流⼯程UMLモデリング (浅海, 2008)

設計/コンポーネント設計(3) 【クラウドアプリケーションのためのオブジェクト指向分析設計講座 第22回】