Fra*_*ing 2 domain-driven-design scala akka
我对Scala和Akka都很陌生,我正在试图弄清楚如何创建一个合适的域模型,它也是一个Actor.
让我们假设我们有一个简单的商业案例,您可以在其中开设一个新的银行账户.假设其中一条规则是您只能为每个姓氏创建一个银行帐户(不现实,但仅为了简单起见).我的第一种方法,没有应用任何业务规则,看起来像这样:
object Main {
def main(args: Array[String]): Unit = {
implicit val system = ActorSystem("accout")
implicit val materializer = ActorMaterializer()
implicit val executionContext = system.dispatcher
val account = system.actorOf(Props[Account])
account ! CreateAccount("Doe")
}
}
case class CreateAccount(lastName: String)
class Account extends Actor {
var lastName: String = null
override def receive: Receive = {
case createAccount: CreateAccount =>
this.lastName = lastName
}
}
Run Code Online (Sandbox Code Playgroud)
最终你会在某个地方保留这些数据.但是,在添加每个姓氏只能有一个银行帐户的规则时,需要对某些数据存储进行查询.假设我们将该逻辑放在存储库中,并且存储库最终返回一个Account,我们得到的问题Account不再是Actor,因为存储库将无法创建Actors.
这绝对是一个错误的实现,而不是如何使用Actors.我的问题是,有什么方法可以解决这些问题?我知道我对Akka的了解还不是很好,所以这可能是一个奇怪/愚蠢的问题.
一般设计
参与者通常应该是业务逻辑的简单调度程序,并且包含尽可能少的功能。认为演员类似于演员Future; 当您想要在Scala中进行并发时,您无需扩展Future类,只需在现有逻辑周围使用Future功能。
限制Actor承担基本责任有几个优点:
业务逻辑(无Akka)
在这里,我们将设置所有特定于域的逻辑,而不使用任何与akka相关的“东西”。
object BusinessLogicDomain {
type FirstName = String
type LastName = String
type Balance = Double
val defaultBalance : Balance = 0.0
case class Account(firstName : FirstName,
lastName : LastName,
balance : Balance = defaultBalance)
Run Code Online (Sandbox Code Playgroud)
让您将帐户目录建模为HashMap:
type AccountDirectory = HashMap[LastName, Account]
val emptyDirectory : AccountDirectory = HashMap.empty[LastName, Account]
Run Code Online (Sandbox Code Playgroud)
现在,我们可以创建一个函数来匹配您对每个姓氏的不同帐户的要求:
val addAccount : (AccountDirectory, Account) => AccountDirectory =
(accountDirectory, account) =>
if(accountDirectory contains account.lastName)
accountDirectory
else
accountDirectory + (account.lastName -> account)
}//end object BusinessLogicDomain
Run Code Online (Sandbox Code Playgroud)
储存库(Akka)
既然完整的业务代码是完整且独立的,我们就可以在基础逻辑之上添加并发层。
我们可以使用becomeActors 的功能来存储状态并响应请求:
import BusinessLogicDomain.{Account, AccountDirectory, emptyDirectory, addAccount}
case object QueryAccountDirectory
class RepoActor(accountDirectory : AccountDirectory = emptyDirectory) extends Actor {
val statefulReceive : AccountDirectory => Receive =
currentDirectory => {
case account : Account =>
context become statefulReceive(addAccount(currentDirectory, account))
case QueryAccountDirectory =>
sender ! currentDirectory
}
override def receive : Receive = statefulReceive(accountDirectory)
}
Run Code Online (Sandbox Code Playgroud)
这可能是一个很长的答案,我很抱歉没有TLDR版本.:)
好的,所以你想"演员化"你的领域模型?馊主意.领域模型不一定是演员.有时他们是,但往往他们不是.每个域模型部署一个actor是一种反模式,因为如果你这样做,你只是卸载调用消息调用的方法,但是丢失了方法调用的所有单线程范例.你不能保证消息的时间到达你的演员和基于ASK模式的编程是一个很好的方式来引入一个不可扩展的系统,最终你有太多的线程和太多的未来,不能继续进一步,系统沼泽和扼流圈.那对你的特定问题意味着什么呢?
首先,您必须停止将域模型视为单一事物,并且绝对不再使用POJO实体.当我讨论贫血领域模型时,我完全赞同马丁福勒.在一个构建良好的演员系统中,通常会有三个领域模型.一个是持久化模型,它具有为数据库建模的实体.第二个是不可变模型.这是演员用来相互沟通的模型.所有实体从下到上是不可变的,所有集合都是不可修改的,所有对象只有getter,所有构造函数都将集合复制到新的不可变集合中.不可变模型意味着你的演员永远不必复制任何东西,他们只是传递对数据的引用.最后,您将拥有API模型,这通常是为客户端使用的JSON建模的实体集.API模型用于将后端与客户端代码更改隔离,反之亦然,它是系统之间的契约.
要创建你的演员,不要再考虑你的持久模型以及你将用它做什么,而是开始考虑用例.你的系统要做什么?根据用例对您的actor进行建模,这将改变actor的实现及其部署策略.
例如,考虑向用户提供库存信息的服务器,包括当前库存水平,用户对用户的评论等等.用户锤击这些信息,随着库存水平的变化,它会迅速变化.该信息可能存储在六个不同的表中.我们不为每个表建模一个actor,而是为这个用例提供单个actor.在这种情况下,这些信息由一大群人在重负载环境中访问.因此,我们最好创建一个actor来聚合所有这些数据并将actor复制到每个节点,每当数据发生变化时,我们都会通知所有节点上的所有复制者.这意味着获得概述的用户甚至不会触摸数据库.他们击中了演员,获得了不可变模型,将其转换为API模型然后返回数据.
另一方面,如果用户想要更改库存水平,我们需要确保两个用户不同时执行此操作,而大型数据库事务会大幅减慢系统速度.因此,我们选择一个节点来保存该供应商的库存管理参与者,然后我们将碎片聚集为参与者.任何请求都会路由到该actor并按顺序处理.公司用户登录并记录收到的20个新项目的交付.消息从它们点击的任何节点传递到持有该供应商的actor的节点,然后供应商进行适当的数据库更改并广播由所有复制的库存视图参与者拾取的更改以更改其数据.
现在这很简单,因为你必须处理丢失的消息(阅读有关为什么不需要可靠消息传递的文章).然而,一旦你开始沿着这条道路走下去,你很快就会意识到简单地让你的领域模型成为一个演员系统是一种反模式,并且有更好的方法来做事.
无论如何这是我的2美分:)
| 归档时间: |
|
| 查看次数: |
617 次 |
| 最近记录: |