Refactoring a Scala Code to Avoid Duplication

joesan

I have the following code that does some pattern matching and returns a response accordingly:

def handleProvisioningServiceCSRequests(provisioningCsRequest: ProvisioningCSRequest, chargingStationId: String, msgTypeId: Int, msgId: String): Future[OCPPCallResponse] = provisioningCsRequest match {
    case bootNotificationRequest: BootNotificationRequest => provisioningService.bootNotification(bootNotificationRequest, chargingStationId).runToFuture.materialize.map {
      case Failure(fail) => populateOCPPCallError(msgId, msgTypeId, OCPPCallErrorCode.InternalError(), fail.getMessage)
      case Success(response) => OCPPCallResult(
        messageTypeId = msgTypeId,
        messageId = msgId,
        payload = Json.toJson(response)
      )
    }
    case notifyReportRequest: NotifyReportRequest => provisioningService.notifyReport(notifyReportRequest, chargingStationId).runToFuture.materialize.map {
      case Failure(fail) => populateOCPPCallError(msgId, msgTypeId, OCPPCallErrorCode.InternalError(), fail.getMessage)
      case Success(response) => OCPPCallResult(
        messageTypeId = msgTypeId,
        messageId = msgId,
        payload = Json.toJson(response)
      )
    }
  }

As it can be seen that the preparation off the OCPPCallError or the OCPPCallResult is repetitive and kind of the same. I will have to do 10 other pattern match case statements and that means 10 times repetition.

I tried refactoring it like this below, but it obviously fails compilation as it says it cannot find implicit conversions:

def handleProvisioningServiceCSRequests(provisioningCsRequest: ProvisioningCSRequest, chargingStationId: String, msgTypeId: Int, msgId: String): Future[OCPPCallResponse] = {
    val result = provisioningCsRequest match {
      case bootNotificationRequest: BootNotificationRequest => provisioningService.bootNotification(bootNotificationRequest, chargingStationId).runToFuture
      case notifyReportRequest: NotifyReportRequest => provisioningService.notifyReport(notifyReportRequest, chargingStationId).runToFuture
    }
    result.materialize.map {
      case Failure(fail) => populateOCPPCallError(msgId, msgTypeId, OCPPCallErrorCode.InternalError(), fail.getMessage)
      case Success(response) => OCPPCallResult(
        messageTypeId = msgTypeId,
        messageId = msgId,
        payload = Json.toJson(response)) // Fails here saying it cannot find implicits for Writes[Product with Serializable]
    }
  }

The response in the Success(response) does not add up. How can I cope up for this?

MartinHH

The most obvious fix would be to ensure that result already contains the converted Json by moving the Json-encoding into the upper pattern match:

def handleProvisioningServiceCSRequests(provisioningCsRequest: ProvisioningCSRequest, chargingStationId: String, msgTypeId: Int, msgId: String): Future[OCPPCallResponse] = {
    val result = provisioningCsRequest match {
      case bootNotificationRequest: BootNotificationRequest => 
        provisioningService.bootNotification(bootNotificationRequest, chargingStationId)
          .runToFuture.map(response => Json.toJson(response))
      case notifyReportRequest: NotifyReportRequest => 
        provisioningService.notifyReport(notifyReportRequest, chargingStationId)
          .runToFuture.map(response => Json.toJson(response))
    }
    result.materialize.map {
      case Failure(fail) => populateOCPPCallError(msgId, msgTypeId, OCPPCallErrorCode.InternalError(), fail.getMessage)
      case Success(json) => OCPPCallResult(
        messageTypeId = msgTypeId,
        messageId = msgId,
        payload = json
    }
  }

Since you're aiming at reducing redundancies, you might also want to move the runToFuture down into the shared part of the code:

def handleProvisioningServiceCSRequests(provisioningCsRequest: ProvisioningCSRequest, chargingStationId: String, msgTypeId: Int, msgId: String): Future[OCPPCallResponse] = {
    val result = provisioningCsRequest match {
      case bootNotificationRequest: BootNotificationRequest => 
        provisioningService.bootNotification(bootNotificationRequest, chargingStationId)
          .map(response => Json.toJson(response))
      case notifyReportRequest: NotifyReportRequest => 
        provisioningService.notifyReport(notifyReportRequest, chargingStationId)
          .map(response => Json.toJson(response))
    }
    result.runToFuture.materialize.map {
      case Failure(fail) => populateOCPPCallError(msgId, msgTypeId, OCPPCallErrorCode.InternalError(), fail.getMessage)
      case Success(json) => OCPPCallResult(
        messageTypeId = msgTypeId,
        messageId = msgId,
        payload = json
    }
  }

Alternatively, a more involved solution would be to introduce a helper function that abstracts over the type of the result and the availability of an implicit Writes instance for it (not 100% sure if this will compile like this because I'm making quite a few assumptions about the libraries that are being used (e.g. assuming that your provisioningService returns monix.eval.Tasks)):

def handleProvisioningServiceCSRequests(provisioningCsRequest: ProvisioningCSRequest, chargingStationId: String, msgTypeId: Int, msgId: String): Future[OCPPCallResponse] = {
    def handleResult[T: Writes](result: Task[T]): Future[OCPPCallResponse] =
      result.runToFuture.materialize.map {
        case Failure(fail) => populateOCPPCallError(msgId, msgTypeId, OCPPCallErrorCode.InternalError(), fail.getMessage)
        case Success(json) => OCPPCallResult(
          messageTypeId = msgTypeId,
          messageId = msgId,
          payload = Json.toJson(response)
      }
    provisioningCsRequest match {
      case bootNotificationRequest: BootNotificationRequest =>
        handleResult(provisioningService.bootNotification(bootNotificationRequest, chargingStationId))
      case notifyReportRequest: NotifyReportRequest => 
        handleResult(provisioningService.notifyReport(notifyReportRequest, chargingStationId))
    }
  }

Edit: if all of your cases are handled in the way that you call a specific method of the provisioningService that takes the specific request and the chargingStationId, then you could make this even less redundant (at the price of making it more verbose). Not sure what type the provisioningService has, so in this example, I name the type ProvisioningService:

def handleProvisioningServiceCSRequests(provisioningCsRequest: ProvisioningCSRequest, chargingStationId: String, msgTypeId: Int, msgId: String): Future[OCPPCallResponse] = {
    def executeRequest[T: Writes](f: ProvisoningService => String => Task[T]): Future[OCPPCallResponse] =
      f(provisoningService)(chargingStationId).runToFuture.materialize.map {
        case Failure(fail) => populateOCPPCallError(msgId, msgTypeId, OCPPCallErrorCode.InternalError(), fail.getMessage)
        case Success(json) => OCPPCallResult(
          messageTypeId = msgTypeId,
          messageId = msgId,
          payload = Json.toJson(response)
      }
    provisioningCsRequest match {
      case bootNotificationRequest: BootNotificationRequest => 
        executeRequest(_.bootNotification(bootNotificationRequest, _))
      case notifyReportRequest: NotifyReportRequest => 
        executeRequest(_.notifyReport(notifyReportRequest, _))
    }
  }

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related