Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
SlideShare a Scribd company logo
Heads-on Domain
Driven Development
in Scala
Yoshitaka Fujii
@yoshiyoshifujii
2016 ScalaMatsuri
Scalaでドメイン駆動設計に真正面から取り組んだ話
Who am I ?
• Yoshitaka Fujii
• @yoshiyoshifujii
• Joined MOTEX in April, 2014
• Web Application Engineer
• Scala (7 months)

Java, Python
自己紹介。エムオーテックス株式会社。Scala歴7ヶ月。
ScalaMatsuri 2016
ScalaMatsuri 2016
We are looking for
a Scala of Engineers !!
Scalaエンジニア募集中!!
ScalaMatsuri 2016
Isn't it difficult?
難しくないですか ?
Design
approach
設計手法に過ぎない。
and
Design
Pattern
あと、デザインパターン
Let’s implement !!
さあ、実装しよう!!
https://goo.gl/DQB6Ak
ScalaMatsuri 2016
too difficult …
難しい…
Today’s
topic
今日、お話したいこと
Amateur
Team
ScalaもDDDも経験の無い私たちのチームが
Hard
Fighting
日々悩み悪戦苦闘した内容について
I'd like to share
what we
learned!!
同じような悩みを持つ皆様の一助になればうれしく思います。
Agenda
• Layered Architecture
• CQRS
• is-a Root Entity
• Message
アジェンダです。
Layered Architecture
レイヤー化アーキテクチャについて
Reprinted from Eric Evans (2003, pp.68) Domain Driven Design.
Review
おさらい
Point of

Layered Architecture
レイヤー化アーキテクチャの要約。
Point of Layered Architecture
1. To clarify the responsibilities of the layer
2. Depends only on the lower layer
3. Isolating the Domain
レイヤーの責務を明確にすること。下位レイヤーだけに依存す
ること。ドメインを隔離すること。
User Interface Layer
• Showing information to the user.
• Interpreting the user’s commands.
• The external actor might sometimes be
another computer system rather than a
human user.
ユーザに情報を表示する。ユーザからのコマンドを解釈する。
外部アクタは別のコンピュータシステムのこともある。
Application Layer
• Defines the jobs the software.
• Directs the expressive domain objects to work
our problems.
• This layer is kept thin.
• It does not contain business rules or
knowledge.
ソフトウェアの仕事を定義、ドメインオブジェクの問題解決を
導く。薄く保つ。ビジネスルールや知識を含まない。
Domain Layer
• Concepts of the business.
• Information about the business solution.
• Business rules.
• This layer is the beat of business software.
ビジネスの概念。ビジネスが置かれた状況に関する情報。ビジ
ネスルール。ビジネスソフトウェアの核心。
Infrastructure Layer
• Provides generic technical capabilities that
support the higher layers.
• Message sending for the application.
• Persistence for the domain.
• Drawing widgets for the UI
上位レイヤを支える技術的機能を提供。Applicationのための
メッセージ送信。Domainのための永続化。UIの描画。
First wall
最初の壁
https://goo.gl/ErmMRx
We wondered how the
tasks within a layer
can be implemented...
どのようにレイヤーの責務を実装しようか…
User Interface Application Domain Infrastructure
User Interface Application Domain Infrastructure
ViewModelForm Entity
Record
SQL
JSON Entity
Result Set
ViewModel
Form
• So-called Form
• It express POST data from view.
• Making corresponding to each View.
いわゆるForm。ViewからのPOST/PUTデータを表す。
case class XxxForm(email: String, name: String)
def form(): Form[XxxForm] =
Form(
mapping(
"email" -> email,
"name" -> nonEmptyText(maxLength = 80))
(XxxForm.apply)(XxxForm.unapply))
ViewModel
• Synonymous with the MVVM pattern
• Holding a state for drawing a view.
• Form => Application => Domain
MVVMパターンのViewModelと同義。Viewを描画するための
状態の保持。UI=>Application=>Domainと受け渡す。
case class XxxForm(email: String, name: String)
case class XxxViewModel(email: String, name: String)
def convertForm2ViewModel(form: XxxForm):
XxxViewModel =
XxxViewModel(
form.email,
form.name)
User Interface Application Domain Infrastructure
ViewModelForm Entity
Record
SQL
JSON Entity
Result Set
ViewModel
User Interface Application Domain Infrastructure
ViewModelForm Entity
Record
SQL
JSON Entity
Result Set
ViewModel
Form =>
ViewModel
ViewModel
=> Entity
Entity => SQL
User Interface Application Domain Infrastructure
ViewModelForm Entity
Record
SQL
JSON Entity
Result Set
ViewModel
Form =>
ViewModel
ViewModel
=> Entity
Entity => SQL
ResultSet =>
Record
Record =>
Entity
Entity =>
ViewModel
ViewModel
=> JSON
So we tried, but...
と、いうことですが…
Second wall
次の壁
https://goo.gl/ErmMRx
When we implemented,
the responsibilities started
to leak across
the boundaries.
実装してみると、責務の境界が、混じるようになりました。
User Interface
x

Domain
たとえば、User Interface に Domain が。
def xxxForm(id: Option[Long]): Form[XxxForm] = Form(
mapping(
"name" -> nonEmptyText(maxLength = 128)
.verifying(“error.uniqueness.name",
!service.isSameNameExisting(_, id)),
"path" -> nonEmptyText(maxLength = 128),
"lockVersion" -> optional(longNumber))
(XxxForm.apply)(XxxForm.unapply))
Application
x
Domain
たとえば、Application に Domain が。
private def validateExists(vm: ViewModel):
Either[Error, ViewModel] = {
repository.findBy(vm.email) match {
case Some(_) => Left("not exists!")
case None => Right(vm)
}
}
Need a plan to make the
boundaries more clear
境界を見失わないための工夫が必要。
Thinking…
どのようにレイヤーの責務を実装しようか…
Communication
コミュニケーション
Review
レビュー
Analysis Modeling
分析モデリング
ScalaMatsuri 2016
ScalaMatsuri 2016
To clarify the responsibilities
of the layer
• Communication
• Review
• Analysis Modeling
レイヤーの責務が混じらないよう、コミュニケーションと
Reviewと分析モデリングを活用した。
Third wall
次の壁
https://goo.gl/ErmMRx
Those pesky Services
曲者のService。
Service
of three types
3つのService
Partitioning Services into Layers
• Application Service
• Domain Service
• Infrastructure Service
アプリケーション層、ドメイン層、インフラ層のそれぞれにサ
ービスってありますよね。
Reprinted from Eric Evans (2003, pp.107) Domain Driven Design.
How do we know which
service to use when?
どう責務を使い分けています?
Overuse of Services leads
to leakage of Domain
knowledge to other layers.
Serviceを多用するとDomainロジックが流出しがち。
Thinking…
3つのServiceをどのように扱うか…
Responsibility of Service
• Once, approach to Application Service.
• Temporarily forgive the outflow of Domain
logic.
• Repeat to consider refactoring.
Application Serviceに寄せる。Domainロジックの流出を一時
的に許容。Refactoringを繰り返し検討。
Refactoring
リファクタリング
Scrum
スクラム
Day of Refactoring
リファクタリングをする日
STOP knowledge leaks
• Refactoring
• Scrum
• Day of Refactoring
ドメインの知識の流出を、リファクタリング、スクラム、リフ
ァクタリングする日を使って継続的に改善した。
Forth wall
次の壁
https://goo.gl/ErmMRx
too many conversion logics
コンバート処理が多いですよね?
User Interface Application Domain Infrastructure
ViewModelForm Entity
Record
SQL
JSON Entity
Result Set
ViewModel
Form =>
ViewModel
ViewModel
=> Entity
Entity => SQL
ResultSet =>
Record
Record =>
Entity
Entity =>
ViewModel
ViewModel
=> JSON
1 2 3
4567
7 conversions
just for a roundtrip
いって、かえってするだけなのに、7回も…
Just live with it!!
仕方ない!!
Some sacrifices
are unavoidable
to isolate the Domains.
Domainを隔離するためなら、多少の犠牲はやむを得ない!!
Domain isolation = DDD !!
Domainを隔離することが、DDDだ!!
Conversion makes the
Domain boundaries clear
Convertすることで、Domainの境界も分かりやすい
Responsibility is
easy to understand
in isolation.
責務が分離され、分かりやすい
Still, too many
conversions...
とはいえ、Convert処理多いよなー
ScalaMatsuri 2016
Command
Query
Responsibility
Segregation
コマンドクエリ責務分離
Agenda
• Layered Architecture
• CQRS
• is-a Root Entity
• Message
CQRS
CQRS
• CQRS stands for 

Command Query Responsibility Segregation
• First proposed by Greg Young
• Martin Fowler
http://martinfowler.com/bliki/CQRS.html
Greg Youngが提唱したのをMartin Fowlerがブログで紹介し
た。
About CQRS
• CQS (Command query separation)
• It states that every method should either be a
command that performs an action
• or a query that returns data to the caller
• but not both
あらゆるメソッドは、「コマンド」もしくは「クエリ」のいず
れか一方で、その両方はダメ。
In the first place…
そもそも…
ScalaMatsuri 2016
Tackling Complexity
in the Heart of Software
ソフトウェアの複雑さに立ち向かう
View the list
一覧を表示する
User Interface Application Domain Infrastructure
ViewModelForm Entity
Record
SQL
JSON Entity
Result Set
ViewModel
Form =>
ViewModel
ViewModel
=> Entity
Entity => SQL
ResultSet =>
Record
Record =>
Entity
Entity =>
ViewModel
ViewModel
=> JSON
more complexity ?
複雑にしてない?
Query Model
呼び出し元にデータを戻す「クエリ」について。
User Interface Application Domain Infrastructure
ViewModelForm
Record
SQL
JSON
Result SetForm =>
ViewModel
ViewModel
=> SQL
ResultSet =>
Record
QueryModel
=> JSON
Record =>
QueryModel
QueryModel
Query Model
• Query Model into the Infrastructure Layer
• Application Layer direct connection to
Infrastructure Layer
• Result Set => Record => Query Model
• Query Model is returned to User Interface
Layer
Infrastructure層にQuery Modelを作る。 Application層から直接
Infrastructure層。 Query ModelはUser Interface層まで持ち込みOK!!
Command Model
何らかのアクションを実行する「コマンド」について。
User Interface Application Domain Infrastructure
ViewModelForm Entity
Record
SQL
JSON Entity
Result Set
ViewModel
Form =>
ViewModel
ViewModel
=> Entity
Entity => SQL
ResultSet =>
Record
Record =>
Entity
Entity =>
ViewModel
ViewModel
=> JSON
Command Model
is simple
Layered Architecture
Command Model は従来のレイヤー化アーキテクチャ
Many of
the business logic
is Command.
ビジネスロジックの多くはCommand
Command tends to be
smaller than Query
Command Modelがシステムの占める割合は少なめ
Summary of CQRS
• Simple retrieval process is separated into
queries.
• Direct connection to the Infrastructure Layer.
• Simple things made simply.
単純な取得処理はクエリとして分離。直接インフラ層にアクセ
ス。シンプルなものはシンプルに作る。
Agenda
• Layered Architecture
• CQRS
• is-a Root Entity
• Message
ROOTエンティティがis-aの関連を持っている場合
is-a Root Entity
ROOTエンティティがis-aの関連を持っている場合
Root Entity
has
is-a
Root Entityをis-aの関連とする設計にすることってありますよ
ね?
ScalaMatsuri 2016
is-a
is confusing
is-aの関連をRoot Entityにするとややこしい。
One Repository ?
or
Two Repository ?
Repositoryは、1つ?2つ?
ScalaMatsuri 2016
Should the parent track the
children?
親が子をしっているの?
ScalaMatsuri 2016
Awkward in terms of OO
オブジェクト指向的に変
Should the Service specify
the children?
クライアントが子を指定するの?
ScalaMatsuri 2016
Awkward in terms of the
aggregation
集約の概念的に変
Take a look
at the source code
コードで見てみる
trait RootEntity {
val id: Option[Long]
val name: String
}
case class Sub1(
id: Option[Long], name: String)
extends RootEntity
case class Sub2(
id: Option[Long], name: String)
extends RootEntity
Parent tracking the children
集約の親が子を知っているパターン
trait RootEntityRepository {
object Sub1Repository { def save(entity: Sub1): Option[Sub1] = ??? }
object Sub2Repository { def save(entity: Sub2): Option[Sub2] = ??? }
def save(types: String, entity: RootEntity) = {
types match {
case "Sub1" => Sub1Repository.save(entity)
case "Sub2" => Sub2Repository.save(entity)
case _ => None
}
}
}
object RootEntityRepository extends RootEntityRepository
trait Service {
def createSub1 = {
val sub1 = Sub1(None, "sub1")
RootEntityRepository.save("Sub1", sub1)
}
def createSub2 = {
val sub2 = Sub2(None, "sub2")
RootEntityRepository.save("Sub2", sub2)
}
}
Service specifying the
children
サービスが子を指定するパターン
trait RootEntityRepository[E <: RootEntity] {}
trait Sub1Repository {}
trait Sub2Repository {}
object Sub1Repository
extends RootEntityRepository[Sub1]
with Sub1Repository
object Sub2Repository
extends RootEntityRepository[Sub2]
with Sub2Repository
trait Service {
def createSub1 = {
val sub1 = Sub1(None, "sub1")
Sub1Repository.save(sub1)
}
def createSub2 = {
val sub2 = Sub2(None, "sub2")
Sub2Repository.save(sub2)
}
}
Which pattern is
more suitable...
どっちのケースが適しているか…
To clarify the hierarchy
どのヒエラルキーに属するRepositoryか明示する。
trait RootEntityRepository[E <: RootEntity] {}
trait Sub1Repository {}
trait Sub2Repository {}
object Sub1Repository
extends RootEntityRepository[Sub1]
with Sub1Repository
object Sub2Repository
extends RootEntityRepository[Sub2]
with Sub2Repository
It eliminates
redundant processing
冗長な処理を簡略化する。
trait RootEntityRepository {
object Sub1Repository { def save(entity: Sub1): Option[Sub1] = ??? }
object Sub2Repository { def save(entity: Sub2): Option[Sub2] = ??? }
def save(types: String, entity: RootEntity) = {
types match {
case "Sub1" => Sub1Repository.save(entity)
case "Sub2" => Sub2Repository.save(entity)
case _ => None
}
}
}
object RootEntityRepository extends RootEntityRepository
Summary of is-a Root Entity
• Service specifying the children
• To clarify the hierarchy
• It eliminates redundant processing
Serviceが子を知っているパターンを採用
Agenda
• Layered Architecture
• Dependency Injection
• is-a Root Entity
• CQRS
• Message
最後にMessageについて。
Message
Low Layer
浅いレイヤーのエラーメッセージはUIに直接返せる
Deep Layer
深いレイヤーのエラーメッセージはそう簡単じゃない
Infrastructure Layer
インフラ層
Unrecoverable
Exception
復旧不可能な例外
Domain Layer
ドメイン層
I want to return
a message
on the screen
なんらかのメッセージを返したい
Type of Message
• Option
• Either
• Try
• Validation
Option
• Some or None. Why None ?
• None is Exception
• Exception is Exception
SomeかNoneで、なぜNoneか分からない。Noneがそもそも
異常なこともある。その場合は例外にしたい。
Either
• Left or Right. Left is error message.
• Monad (salaz)
• Single message only
Left or RightでLeftにメッセージが詰められる。Monadなコ
ーディング可能。ただしメッセージは1つ。
Try
• Define the type of Exception
• Simple error handling
• Throw to the higher layer
例外の型を定義。throw形式でエラー時の制御が容易。処理を
中断し上位のレイヤーまで到達可能。
Validation (Scalaz)
• Return the stack error
• But not Monad
• not be used in for-yield
• Applicative
エラーを詰めて返すことが可能。Monadではない。for式で使
えない。Applicative。
Stack ?
or not
エラー時のメッセージをStackしたいか、したくないか。
Validation
したいならValidation。
or not
Try (or Either)
したくないならTry (or Either)
But …
ただし…
Validation is
Applicative.
not Monad.
ValidationはApplicativeであり、Monadではないよね。
sealed abstract class Validation[+E, +A]
final case class Success[A](a: A)
extends Validation[Nothing, A]
final case class Failure[E](e: E)
extends Validation[E, Nothing]
def ap[EE >: E, B](x: => Validation[EE, A => B])
(implicit E: Semigroup[EE]): Validation[EE, B] = (this, x)
match {
case (Success(a), Success(f)) => Success(f(a))
case (e @ Failure(_), Success(_)) => e
case (Success(f), e @ Failure(_)) => e
case (Failure(e1), Failure(e2)) => Failure(E.append(e2, e1))
}
case class Person(name: String, age: Int)
def validateName(name: String) =
if (name.length > 1) name.successNel
else "invalid name".failureNel
def validateAge(age: Int) =
if (age >= 0) age.successNel
else "invalid age".failureNel
scala> (validateName("Yoshida") |@| validateAge(27))
(Person)
res0: scalaz.Validation[scalaz.NonEmptyList[String],Person]
= Success(Person(Yoshida,27))
scala> (validateName("") |@| validateAge(-1))(Person)
res1: scalaz.Validation[scalaz.NonEmptyList[String],Person]
= Failure(NonEmptyList(invalid name, invalid age))
Summary of Message
• Unrecoverable exception
• Domain Layer return a message to screen
• Validation or Try or Either
復旧不可能な例外もあるが、ドメイン層から何らかのメッセー
ジを返したいとき、ValidationとTry(or Either)を使い分け。
Summary
まとめ
Scala
Scalaで悪戦苦闘
Scala x DDD
ScalaとDDDでさらに苦悩
We hope this was
helpful to you!!
同じような悩みを持つ皆様の一助になればうれしく思います。
Thank you for
your attention.
ご清聴ありがとうございました。

More Related Content

ScalaMatsuri 2016