2012-10-27 34 views
2

在这个时刻,我正面临着我的设计中的一个主要问题。我的方法试图完成以下操作:用依赖关系在scala代码中调用外部服务

  1. 将传入的对象插入到数据库中。
  2. 从插入中获取自动增加的ID并使用它与对象一起调用webservice1。
  3. 从webservice1获取结果,并使用原始对象和webservice1的一些响应调用webservice2。
  4. 结合webservice1和2的结果并将其写入数据库。
  5. 从最后一次插入中获取生成的自动增量标识,并调用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) 
      } 
      } 
     } 
    } 

正如你所看到的,这是一团乱麻。我不能单元测试它,也不能扩展它,也不能很容易地调试它,如果事情失控了。这段代码可以达到它的目的,但是如果能够对如何重构它,让继承我的代码的人们的生活变得更容易一些,我会很高兴。

谢谢

回答

2

看一看scala.util.Try。它在Scala 2.10中可用,它可能会或可能不会作为选项提供给您,但它的想法对您的场景来说是完美的。

你在代码示例中有什么是我喜欢称为嵌套的“金字塔”。最好的解决方案是尽可能使用平面映射。但显然这是一个问题,当你有像Either[Exception, Result]在每一步的东西。这就是Try的地方。Try[T]本质上是Either[Exception, T]的替代品,它配备了您需要的所有flatMap

假设你可以改变这些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 

电梯也有类似的机制在net.liftweb.common.Box。这就像Option,但也有一个例外的容器。

编辑:看起来你可以使用一个Eitherleftright方法,它可以让你使用flatMap -ing几乎一模一样我Try描述的方式。唯一的区别是最终结果是Either[Exception, Result]而不是Try[Result]。有关详细信息/示例,请参阅LeftProjection

+0

谢谢迪伦。这是非常有趣的东西。不幸的是,我陷入了需要重构的2.9.1代码库。有没有一种惯用的scala方式来平面映射到Either上,这会反过来在任何发生异常的发生器上透明地冒泡Exceptions,而不会继续进行其余的计算?另外,“recoverWith”是Try [T]属性吗? –

+0

最后一个问题第一:'recoverWith'确实是'Try [T]'方法。我在答案中链接的文档解释得非常好。至于你的第一个问题,我不知道任何现有的解决方案,直接运行'Either' – Dylan

+0

@sc_ray我看了一下,并编辑了我的答案。希望这会对你有用。 – Dylan

2

您可以用于理解,以减少代码中的噪音。

+1

谢谢。在这里说出无知,但是理解如何处理抛出的异常呢? –

2

@迪兰上面有正确的想法。让我看看我是否可以帮助您将想要做的事情转换为惯用的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 
} 

您可以在斯卡拉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) 

希望这有助于!