简体   繁体   中英

Buisness logic error handling in scala

I'm developing a web service using Scala and Play Framework 2.5. My application has typical layered architecture. I have the following classes: model.User , repository.UserRepository , service.UserService and controllers.UserController and I'm trying to a good way to handle buisness logic errors without using Exception .

Consider the following case: a request for registration a new user was received. There are two parameters in the request body: email and password . These parameters are fed to UserService#registerUser which checks if the email is valid and if user with such email already exists. My solution is to make UserService#registerUser return as a result an Either[BuisnessFailure, Int] object. BuisnessFailure is a trait and is inherited by WrongEmailFormatFailure and UserAlreadyExistFailure . If the email is not valid, UserService#registerUser returns Left(WrongEmailFormatFailure) . If a user with such email already exists, UserService#registerUser returns Left(UserAlreadyExistFailure) . And in case of success, UserService#registerUser returns Right(userRepository.create(User(email, password)) .Afterwards, in controllers.UserController I can handle this case using pattern matching and send an appropriate response.

So, is this approach good enough to handle similar cases? Please, find my code below:

User:

package model
case class User(email: String, password: String)

UserRepository:

package repository

import model.User

class UserRepository {
  def create(user: User): Int = ???
  def find(email: String): Option[User] = ???
}

UserService:

package service

import model.User
import repository.UserRepository
import util.{BuisnessFailure, UserAlreadyExistFailure, WrongEmailFormatFailure}

class UserService {
  private val userRepository: UserRepository = ???
  def registerUser(email: String, password: String): Either[BuisnessFailure, Int] = {
    if (userAlreadyExists(email))
      Left(UserAlreadyExistFailure)
    else
      if (!isEmailValid(email))
        Left(WrongEmailFormatFailure)
      else
        Right(userRepository.create(User(email, password)))

  }
  private def isEmailValid(email: String): Boolean = ???
  private def userAlreadyExists(email: String): Boolean = ???
}

UserController:

package controller

import service.UserService
import util.{UserAlreadyExistFailure, WrongEmailFormatFailure}

class UserController extends play.api.Controller {
  private val userService = new UserService
  def signUp() = Action(parse.json) { implicit request =>
    //obtaining email and password parameters from request body
    val email = ???
    val password = ???
    userService.registerUser(email, password) match {
      case Left(WrongEmailFormatFailure) => // send 400 code and appropriate error message
      case Left(UserAlreadyExistFailure) => // send 400 code and appropriate error message
      case Right(_) => // send response with 200 code
    }
  }
}

BuisnessFailure:

package util

sealed trait BuisnessFailure
case object UserAlreadyExistFailure extends BuisnessFailure
case object WrongEmailFormatFailure extends BuisnessFailure

This is exactly what we've done regarding error handling for one of our biggest projects and we didn't have any problem. Either should be used exactly like that. Left for errors and Right for results.

  • It's typesafe
  • No need to worry about concurrency
  • The code is scalable and maintainable

The only point is that, as most of Scala applications are non-block (async) people would use Future[Either[Error, Int]] rather than Either[Error, Int] . But still, it's OK, as whenever you decided to go for non-blocking you can easily wrap Either inside a Future and as I told you, no worries about concurrency issues.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM