実装
2025年4月17日
浅海智晴
クラウドアプリケーションのための
オブジェクト指向分析設計講座
第46回
ケース・スタディ
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
Testability
Serviceability
• 非機能要件
• Testability
• Serviceability
第1部 基本編の構成(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回]
• 設計/ドメイン設計(1) [第23回]
• 設計/ドメイン設計(2) [第24回]
• 設計/ドメイン設計(3) [第25回]
• 設計/ドメイン設計(4) [第26回]
• 設計/ドメイン設計(5) [第27回]
• 設計/原理 [第28回]
• 設計/ UX/UI設計 [第29回]
• 実装(1) [第30回]
• 実装(2) [第31回]
• 実装(3) [第32回]
• テスト [第33回]
第1部 基本編の構成(2)
• アプリケーション・アーキテクチャ [第34回]
• Cloud Native CBD [第35回]
• ドメイン・サブシステム [第36回]
• アプリケーション・サブシステム [第37回]
• プレゼンテーション・サブシステム [第38回]
• Cloud Native Component Framework [第39回]
• ケーススタディ[第40回]
• ビジネス・モデル [第41回]
• 要求モデル [第42回]
• 要求モデル/BDD [第43回]
• 分析モデル [第44回]
• 設計モデル [第45回]
• 実装 [第46回]
• テスト [第47回]
本講座のアプローチ
• オブジェクト指向分析設計の基本を確認
• 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部 クラウド・アプリケーション編
原理 (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 principles)
• 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, …
内容
• 実装
• 物理モデルを実現
• プラクティス
• Evolve Cloud Native CBD
• User Environment Lite
• CI/CD Pipeline
ケーススタディ
• ブック・カフェPieris Booksの販売システム
• 新刊・古本などの書籍に加えてアクセサリーや日用品などのセ
レクト商品を販売
• もともと新刊・古本の販売も行うブックカフェでしたが、新し
くアクセサリーや日用品のセレクト商品販売を併設することに
した
• セレクト商品は見本品の展示を基本と考えており、見本品に対
してECでオーダをする方式を主に考えている
• 見本品はECサイトでの販売も行う
再掲 第40回 ケーススタディ
開発体制
• 背景
• オブジェクト指向分析設計に興味を持つ関係者がいない環境
• クラウド・アプリケーション開発、アジャイル開発、
• 自分一人でアジャイル開発によるオブジェクト指向開発を進める
• 登場人物
• A(私) : バックエンド開発
• 開発プロセスをマスターして、より大きな規模の開発につなげたい
• Uさん:フロントエンド開発
• 手練れのUIエンジニア。モデリングには興味がなくプログラミング中心で開発を行う
• Bさん:ブック・カフェPieris Booksの店長
• ビジネスでやりたいことは明確にもっているが、ビジネス・モデリング的なアプロー
チには興味がない
再掲 第40回 ケーススタディ
実装
作業分野とモデルの関係 第10回 作業分野
再掲
PracticeのActivity
(マイ開発プロセス版改2)
実装までの流れ
実装の方法論
• 関数モデル [第8回]
• Object-Functional Programming
• 実装(1) [第30回]
• DevOps, CBD, TDD, CI/CDパイプライン
• 実装(2) [第31回]
• 設計の成果物、実装のポイント、TDD
• 実装(3) [第32回]
• CI/CDパイプライン、マイクロサービス、UI、ドキュメント
実装の方針
• Object-Functional Programming
• Scala
• Cloud Native CBD
• Event Centric
• CQRS
• Eventually Consistency
• Cloud Native Component Framework
• Event Centric
• 横断的関心事 (Cross-cutting Concern)
• Security
• 品質属性
アーキテクチャの方針
• 基盤
• Akka
• gRPC : 外部公開
• Tapir : REST
• コンテナ
• Docker/Kubernates
• 3ティア・モデル
• Serverless > Microservice > Monolith
• UIティア
• アプリケーション・ティアでREST APIを提供
• アプリケーション・ティアはUさんに自由に作ってもらう
• アプリケーション・ティア
• Serverless
• ドメイン・ティア
• Microservice
• ただし、複数のドメイン・サブシステムを一つのMicroserviceにまとめてMonolith構造にする
DevSecOpsの方針
• CI/CD Pipeline
• GitHub & AWS
• Observability
• Cloud Native Component Framework
• OpenTelemetoryを想定
• Zero Trust
• Immutable Infrastructure
• IaC (Infrastructure as Code)
• 3R
• Rotate : 定期的な更新・交換
• Repave : クリーン・インストール・再構築
• Repair : 脆弱性や問題を即座に修正
プラクティス
プラクティス
• Cloud Native CBD
• User Environment Light
• CI/CD Pipeline
プロジェクト構造
Cloud Native CBD
Cloud Native CBD
• アクティビティ
• Identify Subsystems
• Make Cloud Native CBD
• Evolve Cloud Native CBD
Evolve Cloud Native CBD
• Make Clud Native CBDで作成した設計モデルをベースに実装
を行う
• プログラム
• テスト・ケース
• TDD
• CI/CDパイプライン
実装の方針
• 要求/分析で作成したBDD/TDD仕様書はそのまま引き継ぎテス
トを継続する
• 仕様変更はBDD/TDDに引き継ぐ
• 開発はTDDで進めTDD仕様書を加えていく
• Servierless
• ServerlessはAWS Lambda Functionを使用
• 当初はMicroserviceとJARを共有し、Lambdaに必要な関数を定義
• 性能上の問題が発生した場合は、Scala JavaScriptコンパイラを使用し
てJavaScriptをNode.js上で動作させて対応予定
Object-Functional Programming (OFP)
• ADT (Algebraic Data Type)
• Product Type, Sum Type
• Monadic Pipeline
• Monadic Pipelineで状態遷移を計算
• 計算した状態遷移をMonadic Pipelineの外側でエンティティに反映
• Monoid
• Domain Datatype
• Type Class
第8回 関数モデル
サービスと関数
第8回 関数モデル
再掲
モナディック・パイプライン
(Monadic Pipeline) 本講座ではモナドを中心とした
マイクロ・フレームワークを
モナディック・パイプラインと呼びます。
第8回 関数モデル
再掲
Scala: for comprehension
• モナディック・パイプラインのためのシンタクスシュガー
def func(a: Int): Int =
val r: Option[Int] = for {
b <- f(a)
c <- g(b)
d = h(c)
e <- i(d)
} yield e
r.get
def f(x: Int): Option[Int] = ???
def g(x: Int): Option[Int] = ???
def h(x: Int): Int = ???
def i(x: Int): Option[Int] = ???
flatMapコンビネータ
mapコンビネータ
第8回 関数モデル
再掲
Object-Oriented Programming (OOP)
• DbC (Design by Contract)
• Domain Datatype
• Domain Powertype
第4回 静的モデル
第4回 静的モデル
第21回 コンポーネント設計(2)
システム・アーキテクチャ
通信基盤
trait EcService extends Subsystem {
def addStock(cmd: AddStockCommand)(using ec: ExecutionContext): Try[Unit]
def addProduct(cmd: AddProductCommand)(using ec: ExecutionContext): Try[Unit]
def listProducts(q: ProductQuery)(using ec: ExecutionContext):
Try[ProductSequence]
def addCart(cmd: AddCartCommand)(using ec: ExecutionContext): Try[AddCartResult]
def listCarts(q: CartQuery)(using ec: ExecutionContext): Try[CartSequence]
def purchase(cmd: PurchaseCommand)(using ec: ExecutionContext):
Try[PurchaseResult]
}
EcService.scala
object Main {
val PORT_GRPC = 8080
val PORT_REST = 9000
private var _engine_option: Option[Engine] = None
private var _ec_option: Option[EcService] = None
def engine = _engine_option getOrElse {
throw new IllegalStateException("No initialize ComponentFramework Engine")
}
def ec = _ec_option getOrElse {
throw new IllegalStateException("No initialize EcService")
}
def initialize(): Unit = {
_engine_option = Some(Engine.create())
_ec_option = Some(EcService.create())
}
def main(args: Array[String]) = {
initialize()
pieris.app.akka.EcServiceActor.run(engine, ec)
pieris.app.grpc.Main.run(PORT_GRPC)
pieris.app.tapir.EcService.run(PORT_REST)
}
}
Main.scala
Akka Actor
• アプリケーション内の基本通信にはAkka Actorを用いる
• 通信が高速
• コンポーネントのインタフェースで使用するCommand/Resultなどの
DTOがそのまま使用できる
class EcServiceActor(engine: Engine, service: EcService) extends Actor {
def receive: Receive = {
case CommandRequest(ec, cmd) => cmd match {
case cmd: AddCartCommand =>
given ctx: CFExecutionContext = engine.toExecutionContext(ec).get
val r = service.addCart(cmd)
sender() ! r
}
}
}
object EcServiceActor {
val config = ConfigFactory.load()
val system: ActorSystem = ActorSystem("SimpleAkkaSystem", config)
given ec: ExecutionContext = system.dispatcher
given timeout: Timeout = Timeout(3.seconds)
def run(engine: Engine, service: EcService) = {
system.actorOf(Props(new EcServiceActor(engine, service)), "EcService")
}
}
}
Akka/EcServiceActor.scala
gRPC
• Akka ActorはJava/Scala以外からの通信は難しい
• Java/Scala同士であっても外部アプリケーションとの相互運用
性を保つのは注意が必要
• アプリケーション外からの利用はgRPC/RESTを用いる
case class AddCartCommand(
procut_id: Product.Id,
count: PositiveQuantity
) extends Command
object AddCartCommand {
def create(id: String, count: Int): AddCartCommand =
AddCartCommand(Product.Id(id), PositiveQuantity(count))
}
AddCartCommand.scala
syntax = "proto3";
package pieris.app.grpc;
service EcService {
rpc addCart (AddCartRequest) returns (AddCartResponse);
}
message AddCartRequest {
bytes execution_context = 1;
string product_id = 2;
int32 count = 3;
}
message AddCartResponse {
string cart_id = 1;
}
gPRC/EcService.proto
object Main extends App {
// Create actor system
given system: ActorSystem = ActorSystem("akka-grpc-server")
given ec: ExecutionContextExecutor = system.dispatcher
given mat: Materializer = Materializer(system)
// Set up the gRPC service handler
val engine = pieris.app.Main.engine
val service = new EcServiceImpl(engine)
val handler = EcServiceHandler(service)
// Bind gRPC service
val grpcBindingFuture: Future[Http.ServerBinding] =
Http(system).newServerAt("localhost", 8080).bind(handler)
}
gRPC/Mian.scala
class EcServiceImpl(engine: Engine)(using mat: Materializer, ec: ExecutionContext)
extends EcService {
override def addCart(in: AddCartRequest): Future[AddCartResponse] = Future {
given ctx: CFExecutionContext =
engine.toExecutionContext(in.executionContext.toByteArray).get
val s = Main.ec
val cmd = AddCartCommand.create(in.productId, in.count)
val r = s.addCart(cmd)
r.map(x => AddCartResponse(x.cart_id.id)).get
}
}
gRPC/EcServieImpl.scala
Tapir
• RESTの実装にTapirを採用
• コンポーネントのインタフェースで使用するCommand/Result
などのDTOがそのまま使用できる
• OpenAPI(Swagger)の仕様出力
val authHeader: EndpointInput.Auth[ExecutionContext, EndpointInput.AuthType.Http] =
auth.bearer[String]().mapDecode { token =>
val engine = pieris.app.Main.engine
engine.toExecutionContext(token).fold(
e => DecodeResult.Error(token, e),
ctx => DecodeResult.Value(ctx)
)
}(ctx => ctx.toToken)
val addCartEndpoint: Endpoint[ExecutionContext, AddCartCommand, Unit, AddCartResult,
Any] = endpoint.
securityIn(authHeader).
in("add_cart").
in(jsonBody[AddCartCommand].description("Cart to purchase")).
out(jsonBody[AddCartResult])
Tapir/Endpoint.scala
object EcService {
val addCartLogic: ExecutionContext => AddCartCommand => IO[AddCartResult] = ctx => cmd => {
val service: EcService = Main.ec
given ExecutionContext = ctx
val r: Try[AddCartResult] = service.addCart(cmd)
IO.fromTry(r)
}
val addCartRoutes: HttpRoutes[IO] =
Http4sServerInterpreter[IO]().toRoutes(
addCartEndpoint.
serverSecurityLogic[ExecutionContext, IO] { ctx =>
IO.pure(Right(ctx))
}.
serverLogic { ctx => cmd =>
addCartLogic(ctx)(cmd).map(Right(_))
}
)
val addCartSwaggerRoutes: HttpRoutes[IO] =
Http4sServerInterpreter[IO]().toRoutes(
SwaggerInterpreter().fromEndpoints[IO](List(addCartEndpoint), "EcService", "1.0")
)
val route = addCartRoutes combineK addCartSwaggerRoutes
val app: HttpApp[IO] = Router("/" -> addCartRoutes).orNotFound
def run(port: Int) = {
val s: IO[Unit] = EmberServerBuilder
.default[IO]
.withHost(com.comcast.ip4s.Host.fromString("localhost").get)
.withPort(com.comcast.ip4s.Port.fromInt(port).get)
.withHttpApp(app)
.build
.useForever
s.unsafeRunSync()
}
}
Tapir/EcService.scala
User Environment Lite
User Environment Lite
• ユーザーの利用環境を整備する
• アクティビティ
• Make Documents
Make Documents
• ユーザー・ガイド
• ユースケースを入力として作成
• ユーザーの目的を達成する手順という観点
• その他、必要な情報を盛り込む
• リファレンス・マニュアル
• コンポーネントの外部インタフェースのリファレンス・マニュアルは
Scaladocで自動生成
• その他
• 運用手順書
• 仕様書
仕様書とプログラム 第32回 実装(3)
再掲
ユースケース駆動開発 第30回 実装(1)
再掲
CI/CD Pipeline
CI/CD Pipeline
• CI/CDパイプラインを構築する
• CI/CDパイプラインに必要な各種成果物の作成を行う
• アクティビティ
• Ensure CI/CD Pipeline to Test
• Ensure CI/CD Pipeline to Deploy
• Ensure CI/CD Pipeline to Delivery
Zero Trustの技術関係図
Ensure CI/CD Pipeline to Test
• CI/CDパイプラインのCI(Continuous Integration)を構築
• CI時にテストが実行され品質が担保されるようにする
• 本プロジェクトではDockerとServerless用のJARファイルの作
成までを行う
enablePlugins(JavaAppPackaging, DockerPlugin)
中略
dockerBaseImage := "openjdk:11-jre-slim",
dockerExposedPorts := Seq(8080, 9000),
mainClass in Compile := Some("pieris.app.Main")
build.sbt(部分)
Ensure CI/CD Pipeline to Deploy
• CI/CDパイプラインのCD(Continuous Delivery)の配備を構築
• 本プロジェクトではGitHub Actionsを用いてDockerイメージを
AWSのECS、Serverless用のJARをAWS Lambdaに配備する
name: Deploy ECS and Lambda
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
env:
AWS_REGION: ${{ secrets.AWS_REGION }}
IMAGE_TAG: ${{ github.sha }}
ECR_REPOSITORY: ${{ secrets.ECR_REPOSITORY }}
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up Java
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'temurin'
- name: Install sbt
uses: coursier/setup-action@v1
with:
apps: sbt
- name: Build Docker image
run: |
sbt docker:publishLocal
docker tag my-scala-app:0.1.0
${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ env.AWS_REGION }}.amazonaws.com/${{ env
.ECR_REPOSITORY }}:${{ env.IMAGE_TAG }}
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
.github/workflows/deploy.yaml
- name: Login to ECR
run: |
aws ecr get-login-password --region $AWS_REGION | docker login --
username AWS --password-stdin
${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.$AWS_REGION.amazonaws.com
- name: Push Docker image to ECR
run: |
docker push
${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ env.AWS_REGION }}.amazonaws.com/${{ env
.ECR_REPOSITORY }}:${{ env.IMAGE_TAG }}
- name: Install CDK
run: npm install -g aws-cdk
- name: Install dependencies & deploy
run: |
cd deploy/cdk
npm install
cdk deploy --require-approval never
- name: Package Scala Lambda
run: |
sbt assembly
mkdir -p lambda-jar
cp target/scala-2.13/scala-lambda-assembly-0.1.0.jar lambda-jar/
- name: Deploy Lambda via CDK
run: |
cd deploy/cdk
npm install
cdk deploy LambdaStack --require-approval never
Ensure CI/CD Pipeline to Deliver
• CI/CDパイプラインのCD(Continuous Delivery)を配布を構築
• 複数のターゲットに配備仕分けるように拡張
• 単体テスト、複合テスト、デモ、製品
• 現段階では配備までの構築を行い、配布は後日行うことにする
• GitHub Actionsで記述することを想定
まとめ
• ケーススタディ
• ブック・カフェPieris Booksの販売システム
• プラクティス
• Cloud Native CBD
• User Environment Light
• CI/CD Pipeline
参考文献
• 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)
• The Essentials of Modern Software
Engineering: Free the Practices from the
Method Prisons! (Jacobson他, 2019)
まとめ
• ケーススタディ
• ブック・カフェPieris Booksの販売システム
• プラクティス
• Make Cloud Native CBD
• アクティビティ
• Define the System Architecture
• Finalize the SubSystem Specification
• Develop the Internal Structure of the SubSystem
• Develop the Domain Model
参考文献
• 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)
• The Essentials of Modern Software
Engineering: Free the Practices from the
Method Prisons! (Jacobson他, 2019)

ケーススタディ/実装 【クラウドアプリケーションのためのオブジェクト指向分析設計講座 第46回】

  • 1.
  • 2.
    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 Testability Serviceability • 非機能要件 • Testability • Serviceability
  • 3.
    第1部 基本編の構成(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回] • 設計/ドメイン設計(1) [第23回] • 設計/ドメイン設計(2) [第24回] • 設計/ドメイン設計(3) [第25回] • 設計/ドメイン設計(4) [第26回] • 設計/ドメイン設計(5) [第27回] • 設計/原理 [第28回] • 設計/ UX/UI設計 [第29回] • 実装(1) [第30回] • 実装(2) [第31回] • 実装(3) [第32回] • テスト [第33回]
  • 4.
    第1部 基本編の構成(2) • アプリケーション・アーキテクチャ[第34回] • Cloud Native CBD [第35回] • ドメイン・サブシステム [第36回] • アプリケーション・サブシステム [第37回] • プレゼンテーション・サブシステム [第38回] • Cloud Native Component Framework [第39回] • ケーススタディ[第40回] • ビジネス・モデル [第41回] • 要求モデル [第42回] • 要求モデル/BDD [第43回] • 分析モデル [第44回] • 設計モデル [第45回] • 実装 [第46回] • テスト [第47回]
  • 5.
    本講座のアプローチ • オブジェクト指向分析設計の基本を確認 • 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部 クラウド・アプリケーション編
  • 6.
    原理 (Principle) • AgileSoftware Development [ASD] • SRP (The Single Responsibility Principle) • OCP (The Open-Close Principle) • LSP (The Liskov Substitution Principle) • … • GRASP (General Responsibility Assignment Software Patterns or principles) • Low Coupling • High Cohesion • … • Writing Effective Use Cases [WEUC] • Scope • …
  • 7.
    パターン (Pattern) • DesignPatterns [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, …
  • 8.
    内容 • 実装 • 物理モデルを実現 •プラクティス • Evolve Cloud Native CBD • User Environment Lite • CI/CD Pipeline
  • 9.
    ケーススタディ • ブック・カフェPieris Booksの販売システム •新刊・古本などの書籍に加えてアクセサリーや日用品などのセ レクト商品を販売 • もともと新刊・古本の販売も行うブックカフェでしたが、新し くアクセサリーや日用品のセレクト商品販売を併設することに した • セレクト商品は見本品の展示を基本と考えており、見本品に対 してECでオーダをする方式を主に考えている • 見本品はECサイトでの販売も行う 再掲 第40回 ケーススタディ
  • 10.
    開発体制 • 背景 • オブジェクト指向分析設計に興味を持つ関係者がいない環境 •クラウド・アプリケーション開発、アジャイル開発、 • 自分一人でアジャイル開発によるオブジェクト指向開発を進める • 登場人物 • A(私) : バックエンド開発 • 開発プロセスをマスターして、より大きな規模の開発につなげたい • Uさん:フロントエンド開発 • 手練れのUIエンジニア。モデリングには興味がなくプログラミング中心で開発を行う • Bさん:ブック・カフェPieris Booksの店長 • ビジネスでやりたいことは明確にもっているが、ビジネス・モデリング的なアプロー チには興味がない 再掲 第40回 ケーススタディ
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
    実装の方法論 • 関数モデル [第8回] •Object-Functional Programming • 実装(1) [第30回] • DevOps, CBD, TDD, CI/CDパイプライン • 実装(2) [第31回] • 設計の成果物、実装のポイント、TDD • 実装(3) [第32回] • CI/CDパイプライン、マイクロサービス、UI、ドキュメント
  • 16.
    実装の方針 • Object-Functional Programming •Scala • Cloud Native CBD • Event Centric • CQRS • Eventually Consistency • Cloud Native Component Framework • Event Centric • 横断的関心事 (Cross-cutting Concern) • Security • 品質属性
  • 17.
    アーキテクチャの方針 • 基盤 • Akka •gRPC : 外部公開 • Tapir : REST • コンテナ • Docker/Kubernates • 3ティア・モデル • Serverless > Microservice > Monolith • UIティア • アプリケーション・ティアでREST APIを提供 • アプリケーション・ティアはUさんに自由に作ってもらう • アプリケーション・ティア • Serverless • ドメイン・ティア • Microservice • ただし、複数のドメイン・サブシステムを一つのMicroserviceにまとめてMonolith構造にする
  • 18.
    DevSecOpsの方針 • CI/CD Pipeline •GitHub & AWS • Observability • Cloud Native Component Framework • OpenTelemetoryを想定 • Zero Trust • Immutable Infrastructure • IaC (Infrastructure as Code) • 3R • Rotate : 定期的な更新・交換 • Repave : クリーン・インストール・再構築 • Repair : 脆弱性や問題を即座に修正
  • 19.
  • 20.
    プラクティス • Cloud NativeCBD • User Environment Light • CI/CD Pipeline
  • 21.
  • 22.
  • 23.
    Cloud Native CBD •アクティビティ • Identify Subsystems • Make Cloud Native CBD • Evolve Cloud Native CBD
  • 24.
    Evolve Cloud NativeCBD • Make Clud Native CBDで作成した設計モデルをベースに実装 を行う • プログラム • テスト・ケース • TDD • CI/CDパイプライン
  • 25.
    実装の方針 • 要求/分析で作成したBDD/TDD仕様書はそのまま引き継ぎテス トを継続する • 仕様変更はBDD/TDDに引き継ぐ •開発はTDDで進めTDD仕様書を加えていく • Servierless • ServerlessはAWS Lambda Functionを使用 • 当初はMicroserviceとJARを共有し、Lambdaに必要な関数を定義 • 性能上の問題が発生した場合は、Scala JavaScriptコンパイラを使用し てJavaScriptをNode.js上で動作させて対応予定
  • 26.
    Object-Functional Programming (OFP) •ADT (Algebraic Data Type) • Product Type, Sum Type • Monadic Pipeline • Monadic Pipelineで状態遷移を計算 • 計算した状態遷移をMonadic Pipelineの外側でエンティティに反映 • Monoid • Domain Datatype • Type Class 第8回 関数モデル
  • 27.
  • 28.
  • 29.
    Scala: for comprehension •モナディック・パイプラインのためのシンタクスシュガー def func(a: Int): Int = val r: Option[Int] = for { b <- f(a) c <- g(b) d = h(c) e <- i(d) } yield e r.get def f(x: Int): Option[Int] = ??? def g(x: Int): Option[Int] = ??? def h(x: Int): Int = ??? def i(x: Int): Option[Int] = ??? flatMapコンビネータ mapコンビネータ 第8回 関数モデル 再掲
  • 30.
    Object-Oriented Programming (OOP) •DbC (Design by Contract) • Domain Datatype • Domain Powertype 第4回 静的モデル 第4回 静的モデル 第21回 コンポーネント設計(2)
  • 31.
  • 32.
  • 33.
    trait EcService extendsSubsystem { def addStock(cmd: AddStockCommand)(using ec: ExecutionContext): Try[Unit] def addProduct(cmd: AddProductCommand)(using ec: ExecutionContext): Try[Unit] def listProducts(q: ProductQuery)(using ec: ExecutionContext): Try[ProductSequence] def addCart(cmd: AddCartCommand)(using ec: ExecutionContext): Try[AddCartResult] def listCarts(q: CartQuery)(using ec: ExecutionContext): Try[CartSequence] def purchase(cmd: PurchaseCommand)(using ec: ExecutionContext): Try[PurchaseResult] } EcService.scala
  • 34.
    object Main { valPORT_GRPC = 8080 val PORT_REST = 9000 private var _engine_option: Option[Engine] = None private var _ec_option: Option[EcService] = None def engine = _engine_option getOrElse { throw new IllegalStateException("No initialize ComponentFramework Engine") } def ec = _ec_option getOrElse { throw new IllegalStateException("No initialize EcService") } def initialize(): Unit = { _engine_option = Some(Engine.create()) _ec_option = Some(EcService.create()) } def main(args: Array[String]) = { initialize() pieris.app.akka.EcServiceActor.run(engine, ec) pieris.app.grpc.Main.run(PORT_GRPC) pieris.app.tapir.EcService.run(PORT_REST) } } Main.scala
  • 35.
    Akka Actor • アプリケーション内の基本通信にはAkkaActorを用いる • 通信が高速 • コンポーネントのインタフェースで使用するCommand/Resultなどの DTOがそのまま使用できる
  • 36.
    class EcServiceActor(engine: Engine,service: EcService) extends Actor { def receive: Receive = { case CommandRequest(ec, cmd) => cmd match { case cmd: AddCartCommand => given ctx: CFExecutionContext = engine.toExecutionContext(ec).get val r = service.addCart(cmd) sender() ! r } } } object EcServiceActor { val config = ConfigFactory.load() val system: ActorSystem = ActorSystem("SimpleAkkaSystem", config) given ec: ExecutionContext = system.dispatcher given timeout: Timeout = Timeout(3.seconds) def run(engine: Engine, service: EcService) = { system.actorOf(Props(new EcServiceActor(engine, service)), "EcService") } } } Akka/EcServiceActor.scala
  • 37.
    gRPC • Akka ActorはJava/Scala以外からの通信は難しい •Java/Scala同士であっても外部アプリケーションとの相互運用 性を保つのは注意が必要 • アプリケーション外からの利用はgRPC/RESTを用いる
  • 38.
    case class AddCartCommand( procut_id:Product.Id, count: PositiveQuantity ) extends Command object AddCartCommand { def create(id: String, count: Int): AddCartCommand = AddCartCommand(Product.Id(id), PositiveQuantity(count)) } AddCartCommand.scala
  • 39.
    syntax = "proto3"; packagepieris.app.grpc; service EcService { rpc addCart (AddCartRequest) returns (AddCartResponse); } message AddCartRequest { bytes execution_context = 1; string product_id = 2; int32 count = 3; } message AddCartResponse { string cart_id = 1; } gPRC/EcService.proto
  • 40.
    object Main extendsApp { // Create actor system given system: ActorSystem = ActorSystem("akka-grpc-server") given ec: ExecutionContextExecutor = system.dispatcher given mat: Materializer = Materializer(system) // Set up the gRPC service handler val engine = pieris.app.Main.engine val service = new EcServiceImpl(engine) val handler = EcServiceHandler(service) // Bind gRPC service val grpcBindingFuture: Future[Http.ServerBinding] = Http(system).newServerAt("localhost", 8080).bind(handler) } gRPC/Mian.scala
  • 41.
    class EcServiceImpl(engine: Engine)(usingmat: Materializer, ec: ExecutionContext) extends EcService { override def addCart(in: AddCartRequest): Future[AddCartResponse] = Future { given ctx: CFExecutionContext = engine.toExecutionContext(in.executionContext.toByteArray).get val s = Main.ec val cmd = AddCartCommand.create(in.productId, in.count) val r = s.addCart(cmd) r.map(x => AddCartResponse(x.cart_id.id)).get } } gRPC/EcServieImpl.scala
  • 42.
  • 43.
    val authHeader: EndpointInput.Auth[ExecutionContext,EndpointInput.AuthType.Http] = auth.bearer[String]().mapDecode { token => val engine = pieris.app.Main.engine engine.toExecutionContext(token).fold( e => DecodeResult.Error(token, e), ctx => DecodeResult.Value(ctx) ) }(ctx => ctx.toToken) val addCartEndpoint: Endpoint[ExecutionContext, AddCartCommand, Unit, AddCartResult, Any] = endpoint. securityIn(authHeader). in("add_cart"). in(jsonBody[AddCartCommand].description("Cart to purchase")). out(jsonBody[AddCartResult]) Tapir/Endpoint.scala
  • 44.
    object EcService { valaddCartLogic: ExecutionContext => AddCartCommand => IO[AddCartResult] = ctx => cmd => { val service: EcService = Main.ec given ExecutionContext = ctx val r: Try[AddCartResult] = service.addCart(cmd) IO.fromTry(r) } val addCartRoutes: HttpRoutes[IO] = Http4sServerInterpreter[IO]().toRoutes( addCartEndpoint. serverSecurityLogic[ExecutionContext, IO] { ctx => IO.pure(Right(ctx)) }. serverLogic { ctx => cmd => addCartLogic(ctx)(cmd).map(Right(_)) } ) val addCartSwaggerRoutes: HttpRoutes[IO] = Http4sServerInterpreter[IO]().toRoutes( SwaggerInterpreter().fromEndpoints[IO](List(addCartEndpoint), "EcService", "1.0") ) val route = addCartRoutes combineK addCartSwaggerRoutes val app: HttpApp[IO] = Router("/" -> addCartRoutes).orNotFound def run(port: Int) = { val s: IO[Unit] = EmberServerBuilder .default[IO] .withHost(com.comcast.ip4s.Host.fromString("localhost").get) .withPort(com.comcast.ip4s.Port.fromInt(port).get) .withHttpApp(app) .build .useForever s.unsafeRunSync() } } Tapir/EcService.scala
  • 45.
  • 46.
    User Environment Lite •ユーザーの利用環境を整備する • アクティビティ • Make Documents
  • 47.
    Make Documents • ユーザー・ガイド •ユースケースを入力として作成 • ユーザーの目的を達成する手順という観点 • その他、必要な情報を盛り込む • リファレンス・マニュアル • コンポーネントの外部インタフェースのリファレンス・マニュアルは Scaladocで自動生成 • その他 • 運用手順書 • 仕様書
  • 48.
  • 49.
  • 50.
  • 51.
    CI/CD Pipeline • CI/CDパイプラインを構築する •CI/CDパイプラインに必要な各種成果物の作成を行う • アクティビティ • Ensure CI/CD Pipeline to Test • Ensure CI/CD Pipeline to Deploy • Ensure CI/CD Pipeline to Delivery
  • 52.
  • 53.
    Ensure CI/CD Pipelineto Test • CI/CDパイプラインのCI(Continuous Integration)を構築 • CI時にテストが実行され品質が担保されるようにする • 本プロジェクトではDockerとServerless用のJARファイルの作 成までを行う
  • 54.
    enablePlugins(JavaAppPackaging, DockerPlugin) 中略 dockerBaseImage :="openjdk:11-jre-slim", dockerExposedPorts := Seq(8080, 9000), mainClass in Compile := Some("pieris.app.Main") build.sbt(部分)
  • 55.
    Ensure CI/CD Pipelineto Deploy • CI/CDパイプラインのCD(Continuous Delivery)の配備を構築 • 本プロジェクトではGitHub Actionsを用いてDockerイメージを AWSのECS、Serverless用のJARをAWS Lambdaに配備する
  • 56.
    name: Deploy ECSand Lambda on: push: branches: - main jobs: deploy: runs-on: ubuntu-latest env: AWS_REGION: ${{ secrets.AWS_REGION }} IMAGE_TAG: ${{ github.sha }} ECR_REPOSITORY: ${{ secrets.ECR_REPOSITORY }} steps: - name: Checkout uses: actions/checkout@v3 - name: Set up Java uses: actions/setup-java@v3 with: java-version: '11' distribution: 'temurin' - name: Install sbt uses: coursier/setup-action@v1 with: apps: sbt - name: Build Docker image run: | sbt docker:publishLocal docker tag my-scala-app:0.1.0 ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ env.AWS_REGION }}.amazonaws.com/${{ env .ECR_REPOSITORY }}:${{ env.IMAGE_TAG }} - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v2 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: ${{ env.AWS_REGION }} .github/workflows/deploy.yaml - name: Login to ECR run: | aws ecr get-login-password --region $AWS_REGION | docker login -- username AWS --password-stdin ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.$AWS_REGION.amazonaws.com - name: Push Docker image to ECR run: | docker push ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ env.AWS_REGION }}.amazonaws.com/${{ env .ECR_REPOSITORY }}:${{ env.IMAGE_TAG }} - name: Install CDK run: npm install -g aws-cdk - name: Install dependencies & deploy run: | cd deploy/cdk npm install cdk deploy --require-approval never - name: Package Scala Lambda run: | sbt assembly mkdir -p lambda-jar cp target/scala-2.13/scala-lambda-assembly-0.1.0.jar lambda-jar/ - name: Deploy Lambda via CDK run: | cd deploy/cdk npm install cdk deploy LambdaStack --require-approval never
  • 57.
    Ensure CI/CD Pipelineto Deliver • CI/CDパイプラインのCD(Continuous Delivery)を配布を構築 • 複数のターゲットに配備仕分けるように拡張 • 単体テスト、複合テスト、デモ、製品 • 現段階では配備までの構築を行い、配布は後日行うことにする • GitHub Actionsで記述することを想定
  • 58.
    まとめ • ケーススタディ • ブック・カフェPierisBooksの販売システム • プラクティス • Cloud Native CBD • User Environment Light • CI/CD Pipeline
  • 59.
    参考文献 • The UnifiedModeling 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) • The Essentials of Modern Software Engineering: Free the Practices from the Method Prisons! (Jacobson他, 2019)
  • 60.
    まとめ • ケーススタディ • ブック・カフェPierisBooksの販売システム • プラクティス • Make Cloud Native CBD • アクティビティ • Define the System Architecture • Finalize the SubSystem Specification • Develop the Internal Structure of the SubSystem • Develop the Domain Model
  • 61.
    参考文献 • The UnifiedModeling 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) • The Essentials of Modern Software Engineering: Free the Practices from the Method Prisons! (Jacobson他, 2019)