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.
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.