问题 从控制器重构业务逻辑的良好,惯用的方法
我是Scala和Play的新手;我写了一个包含业务和表示逻辑的“全部”控制器。我想从控制器中重构业务逻辑。
这就是我的Scala / Play的样子。使用干净的界面从这个控制器重构业务逻辑的好/惯用方法是什么?
object NodeRender extends Controller {
...
def deleteNode(nodeId: Long) = Action { request =>
//business logic
val commitDocument = Json.toJson(
Map(
"delete" -> Seq( Map( "id" -> toJson( nodeId)))
))
val commitSend = Json.stringify( commitDocument)
val commitParams = Map( "commit" -> "true", "wt" -> "json")
val headers = Map( "Content-type" -> "application/json")
val sol = host( "127.0.0.1", 8080)
val updateReq = sol / "solr-store" / "collection1" / "update" / "json" <<?
commitParams <:< headers << commitSend
val commitResponse = Http( updateReq)()
//presentation logic
Redirect( routes.NodeRender.listNodes)
}
在Python / Django中,我写了两个类 XApiHandler
和 XBackend
并在它们之间使用干净的界面。
xb = XBackend( user).do_stuff()
if not xb:
return a_404_error
else:
return the_right_stuff( xb.content) #please dont assume its a view!
3985
2018-03-05 18:02
起源
答案:
一些假设:
1)最后一行的HTTP调用阻塞
2)你没有说重定向是否需要等待来自Http调用的响应,但我认为它确实如此。
阻止调用应该移动到另一个线程,这样你就不会阻塞处理请求的线程。 Play文档对此非常具体。该 Akka.future
功能结合 Async
帮助。
控制器代码:
1 def deleteNode(nodeId: Long) = Action { request =>
2 Async{
3 val response = Akka.future( BusinessService.businessLogic(nodeId) )
4
5 response.map { result =>
6 result map {
7 Redirect( routes.NodeRender.listNodes)
8 } recover {
9 InternalServerError("Failed due to ...")
10 } get
11 }
12 }
13}
这比你的PHP多一点,但它是多线程的。
代码传递给 Akka.future
第3行将在未来的某个时间使用不同的线程调用。但呼吁 Akka.future
立即返回一个 Future[Try]
(请参阅下面的业务方法的返回类型)。这意味着变量 response
有类型 Future[Try]
。打电话给 map
第5行的方法不调用map块中的代码,而是将该代码(第6-10行)注册为回调。线程不会在第5行阻塞并返回 Future
到了 Async
块。该 Async
块返回一个 AsyncResult
播放并告诉Play在未来完成时注册自己的回调。
同时,其他一些线程会调用 BusinessService
从第3行开始,一旦你对后端系统发出的HTTP调用返回, response
第3行的变量是“已完成”,意味着第6-10行的回调被调用。 result
有类型 Try
这是抽象的,只有两个子类: Success
和 Failure
。如果 result
是成功,然后是 map
方法调用第7行并将其包装成新的 Success
。如果 result
是失败,然后map方法返回失败。该 recover
第8行的方法则相反。如果map方法的结果是成功的,那么它返回成功,否则它调用第9行并将其包装在a中 Success
(不是 Failure
!)。打电话给 get
第10行的方法将重定向或错误取出 Success
并且该值用于完成 AsyncResult
Play正在坚持下去。播放然后获得回复,响应已准备好并可以呈现和发送。
使用此解决方案,不会阻止为传入请求提供服务的线程。这很重要,因为例如在4核机器上,Play只有8个线程能够处理传入的请求。它不会产生任何新的,至少在使用默认配置时不会。
以下是Business Service对象的代码(几乎复制了您的代码):
def businessLogic(nodeId: Long): Future[Try] {
val commitDocument = Json.toJson(
Map(
"delete" -> Seq( Map( "id" -> toJson( nodeId)))
))
val commitSend = Json.stringify( commitDocument)
val commitParams = Map( "commit" -> "true", "wt" -> "json")
val headers = Map( "Content-type" -> "application/json")
val sol = host( "127.0.0.1", 8080)
val updateReq = sol / "solr-store" / "collection1" / "update" / "json" <<?
commitParams <:< headers << commitSend
val commitResponse = Http( updateReq)()
Success(commitResponse) //return the response or null, doesnt really matter so long as its wrapped in a successful Try
}
现在,表示逻辑和业务逻辑完全分离。
看到 https://speakerdeck.com/heathermiller/futures-and-promises-in-scala-2-dot-10 和 http://docs.scala-lang.org/overviews/core/futures.html 了解更多信息。
7
2018-03-09 21:25
答案:
一些假设:
1)最后一行的HTTP调用阻塞
2)你没有说重定向是否需要等待来自Http调用的响应,但我认为它确实如此。
阻止调用应该移动到另一个线程,这样你就不会阻塞处理请求的线程。 Play文档对此非常具体。该 Akka.future
功能结合 Async
帮助。
控制器代码:
1 def deleteNode(nodeId: Long) = Action { request =>
2 Async{
3 val response = Akka.future( BusinessService.businessLogic(nodeId) )
4
5 response.map { result =>
6 result map {
7 Redirect( routes.NodeRender.listNodes)
8 } recover {
9 InternalServerError("Failed due to ...")
10 } get
11 }
12 }
13}
这比你的PHP多一点,但它是多线程的。
代码传递给 Akka.future
第3行将在未来的某个时间使用不同的线程调用。但呼吁 Akka.future
立即返回一个 Future[Try]
(请参阅下面的业务方法的返回类型)。这意味着变量 response
有类型 Future[Try]
。打电话给 map
第5行的方法不调用map块中的代码,而是将该代码(第6-10行)注册为回调。线程不会在第5行阻塞并返回 Future
到了 Async
块。该 Async
块返回一个 AsyncResult
播放并告诉Play在未来完成时注册自己的回调。
同时,其他一些线程会调用 BusinessService
从第3行开始,一旦你对后端系统发出的HTTP调用返回, response
第3行的变量是“已完成”,意味着第6-10行的回调被调用。 result
有类型 Try
这是抽象的,只有两个子类: Success
和 Failure
。如果 result
是成功,然后是 map
方法调用第7行并将其包装成新的 Success
。如果 result
是失败,然后map方法返回失败。该 recover
第8行的方法则相反。如果map方法的结果是成功的,那么它返回成功,否则它调用第9行并将其包装在a中 Success
(不是 Failure
!)。打电话给 get
第10行的方法将重定向或错误取出 Success
并且该值用于完成 AsyncResult
Play正在坚持下去。播放然后获得回复,响应已准备好并可以呈现和发送。
使用此解决方案,不会阻止为传入请求提供服务的线程。这很重要,因为例如在4核机器上,Play只有8个线程能够处理传入的请求。它不会产生任何新的,至少在使用默认配置时不会。
以下是Business Service对象的代码(几乎复制了您的代码):
def businessLogic(nodeId: Long): Future[Try] {
val commitDocument = Json.toJson(
Map(
"delete" -> Seq( Map( "id" -> toJson( nodeId)))
))
val commitSend = Json.stringify( commitDocument)
val commitParams = Map( "commit" -> "true", "wt" -> "json")
val headers = Map( "Content-type" -> "application/json")
val sol = host( "127.0.0.1", 8080)
val updateReq = sol / "solr-store" / "collection1" / "update" / "json" <<?
commitParams <:< headers << commitSend
val commitResponse = Http( updateReq)()
Success(commitResponse) //return the response or null, doesnt really matter so long as its wrapped in a successful Try
}
现在,表示逻辑和业务逻辑完全分离。
看到 https://speakerdeck.com/heathermiller/futures-and-promises-in-scala-2-dot-10 和 http://docs.scala-lang.org/overviews/core/futures.html 了解更多信息。
7
2018-03-09 21:25
我可能会这样做
object NodeRenderer extends Controller {
def listNodes = Action { request =>
Ok("list")
}
def deleteNode(nodeId: Long)(
implicit nodeService: NodeService = NodeService) = Action { request =>
Async {
Future {
val response = nodeService.deleteNode(nodeId)
response.apply.fold(
error => BadRequest(error.message),
success => Redirect(routes.NodeRenderer.listNodes))
}
}
}
}
节点服务文件看起来像这样
trait NodeService {
def deleteNode(nodeId: Long): Promise[Either[Error, Success]]
}
object NodeService extends NodeService {
val deleteDocument =
(__ \ "delete").write(
Writes.seq(
(__ \ "id").write[Long]))
val commitParams = Map("commit" -> "true", "wt" -> "json")
val headers = Map("Content-type" -> "application/json")
def sol = host("127.0.0.1", 8080)
def baseReq = sol / "solr-store" / "collection1" / "update" / "json" <<?
commitParams <:< headers
def deleteNode(nodeId: Long): Promise[Either[Error, Success]] = {
//business logic
val commitDocument =
deleteDocument
.writes(Seq(nodeId))
.toString
val updateReq = baseReq << commitDocument
Http(updateReq).either.map(
_.left.map(e => Error(e.getMessage))
.right.map(r => Success))
}
}
我定义的地方 Error
和 Success
喜欢这个
case class Error(message: String)
trait Success
case object Success extends Success
这将您的http部分和业务逻辑分开,允许您为同一服务创建其他类型的前端。同时它允许您在提供模拟时测试您的http处理 NodeService
。
如果你需要有不同类型的 NodeService
绑定到您可能转换的同一个控制器 NodeRenderer
到一个类并使用构造函数传递它。 这个例子 告诉你如何做到这一点。
4
2018-03-07 22:32
我不是专家,但我很高兴将相干逻辑块分解为混合特性。
abstract class CommonBase {
def deleteNode(): Unit
}
trait Logic extends CommonBase{
this: NodeRender =>
override def deleteNode(): Unit = {
println("Logic Here")
println(CoolString)
}
}
class NodeRender extends CommonBase
with Logic
{
val CoolString = "Hello World"
}
object test {
def main(args: Array[String]) {
println("starting ...")
(new NodeRender()).deleteNode()
}
}
版画
starting ...
Logic Here
Hello World
1
2018-03-07 17:10