2

在这个关头,我的设计面临一个重大问题。我的方法是尝试完成以下操作:

  1. 将传入的对象插入数据库。
  2. 从插入中获取自动递增的 id,并使用它与对象一起调用 webservice1。
  3. 从 webservice1 获取结果并使用原始对象和来自 webservice1 的一些响应调用 webservice2。
  4. 将 webservice1 和 2 的结果结合起来,写入数据库。
  5. 从最后一次插入中获取生成的自动递增 id,并使用最终会导致操作成功或失败的原始对象调用 webservice3。

我想以灵活的方式设计它,因为需求在不断变化,我不想继续根据任何变化修改我的逻辑。我确实意识到一些变化是不可避免的,但我想尽量减少损害并尊重开闭原则。

我最初的看法如下:

def complexOperation(someObject:T) = 
   dbService.insertIntoDb(someObject) match {
     case Left(e:Exception) => Left(e)
     case Right(id:Int) => webService.callWebService1(id,someObject) match {
        case Left(e:Exception) => Left(e)
        case Right(r:SomeResponse1) => webService.callWebservice2(r,someObject) match {
          case Left(e:Exception) => webService.rollbackService1();Left(e)
          case Right(context:ResponseContext) => dbService.insertContextIntoDb(context) match {
            case Left(e:Exception) => Left(e)
            case Right(id:Int) => webService.callWebservice3(id,someObject) match {
               case Left(e:Exception) => webService.rollbackService3();Left(e)
               case Right(r:Response) => Right(r)
            }
          }
        } 
     }

如您所见,这是一团乱麻。如果事情失控,我既不能对它进行单元测试,也不能扩展它,也不能很容易地调试它。这段代码达到了它的目的,但如果能获得一些关于我应该如何重构它以使继承我的代码的人的生活更轻松一些的想法将会很棒。

谢谢

4

3 回答 3

2

您可以使用 for comprehension 来减少代码中的噪音。

于 2012-10-28T01:34:43.590 回答
2

看看scala.util.Try。它在 Scala 2.10 中可用,您可能会也可能不会选择它,但它的想法非常适合您的场景。

您在代码示例中拥有的是我喜欢称之为嵌套的“金字塔”的东西。最好的解决方案是尽可能使用平面映射。但显然,当你Either[Exception, Result]在每一步都有类似的东西时,这是一个问题。这就是Try出现的地方。Try[T]本质上是 的替代品Either[Exception, T],并且它具有flatMap您需要的所有优点。

假设您可以更改这些调用的返回类型,或者提供一些从to的webService隐式转换,您的代码块将变得更像......Either[Exception, Result]Try[Result]

for {
    id <- dbService.insertIntoDb(someObject)
    r <- webService.callWebService1(id,someObject)
    context <- webService.callWebservice2(r,someObject)
    id2 <- dbService.insertContextIntoDb(context)
    response <- webService.callWebservice3(id,someObject).recoverWith {
        case e: Exception => webService.rollbackService3(); Failure(e)
    }
} yield response

Lift 在net.liftweb.common.Box中也有类似的机制。就像Option,但也有一个异常容器。

编辑:看起来你可以使用leftorright的方法Either,它会让你flatMap几乎完全按照我描述的方式使用 -ing Try。唯一的区别是最终结果是 aEither[Exception, Result]而不是 a Try[Result]。查看LeftProjection了解详细信息/示例。

于 2012-10-28T01:46:20.790 回答
2

@Dylan 上面有正确的想法。让我看看我是否可以帮助将您想做的事情翻译成惯用的 Scala 2.9.1 代码。

此版本不尝试任何回滚:

// 1: No rollbacks, just returns the first exception in Left
def complexOperation1(someObject:T): Either[Exception, Response] = {      
  for {
    id       <- dbService.insertIntoDb(someObject).right
    r        <- webService.callWebService1(id, someObject).right
    context  <- webService.callWebservice2(idResp, someObject).right
    id2      <- dbService.insertContextIntoDb(context).right
    response <- webService.callWebservice3(id,someObject).right
  } yield response 
}

现在,让我们尝试完全按照上面的方式进行回滚:

// 2: Rolls back all web services and returns first exception in Left       
def complexOperation1(someObject:T): Either[Exception, Response] = {      
  for {
    id       <- dbService.insertIntoDb(someObject).right
    r        <- webService.callWebService1(id, someObject).right
    context  <- webService.callWebservice2(idResp, someObject).left.map { e =>
                  webService.rollbackService1()
                  e
                }.right
    id2      <- dbService.insertContextIntoDb(context).right
    response <- webService.callWebservice3(id,someObject).left.map { e =>
                  webService.rollbackService3()
                  e
                }.right
  } yield response 
}

如果你定义一个函数来实现左边的效果(回滚),它会变得更干净,更容易测试,例如:

// 3: Factor out the side-effect of doing the follbacks on Left
def rollbackIfLeft[T](f: => Either[Exception, T], r: => Unit): Either[Exception, T] = {
  val result = f
  result.left.foreach(_ => r) // do the rollback if any exception occured
  result
}

def complexOperation1(someObject:T): Either[Exception, Response] = {      
  for {
    id       <- dbService.insertIntoDb(someObject).right
    r        <- webService.callWebService1(id, someObject).right
    context  <- rollbackIfLeft(webService.callWebservice2(idResp, someObject),
                               webService.rollbackService1()).right
    id2      <- dbService.insertContextIntoDb(context).right
    response <- rollbackIfLeft(webService.callWebservice3(id,someObject),
                               webService.rollbackService3()).right
  } yield response 
}

您可以在 scala REPL 中试用rollbackIfLeft以了解它:

scala> rollbackIfLeft(Right(42), println("hey"))
res28: Either[Exception,Int] = Right(42)

scala> rollbackIfLeft(Left(new RuntimeException), println("ERROR!"))
ERROR!
res29: Either[Exception,Nothing] = Left(java.lang.RuntimeException)

希望这可以帮助!

于 2013-02-06T22:31:31.357 回答