2016-10-27 19 views
7

Như đã đề cập trong tiêu đề, bạn có nên sử dụng cấu trúc dữ liệu đó không? Hãy để tôi giải thích từng người một:Tương lai [Hoặc [AppError, Tùy chọn [Người dùng]]] trong Scala

  • Future - đại diện cho async tính toán
  • Hoặc - để giao tiếp lỗi tiếng
  • Tùy chọn - để giao tiếp rằng giá trị có thể không có mặt

Tôi một chút sợ hãi khi nhìn vào điều này. Nó là một thực hành tốt để sử dụng kết hợp loại như vậy?

+1

Không có gì sai với nó, có bạn có thể có một chút của một loại phức tạp hơn nhưng nó không đại diện cho logic kinh doanh của bạn (tôi cũng có một cái gì đó như thế này), nếu bạn quan tâm đến cách xử lý tốt hơn điều này có một cái nhìn tại 'Disjunction' (thay vì' Either') và 'EitherT' từ scalaz về cách chuyển đổi như một đơn lẻ và chỉ phải đối phó với 'Tùy chọn'. –

+1

Tôi đã viết một dịch vụ web tại nơi làm việc bằng cách sử dụng loại này thường. Có lẽ bạn sẽ tìm thấy [thảo luận] này (https://www.reddit.com/r/scala/comments/3r5ii6/on_handling_futureeithera_b_sequencing/) thú vị. Đặc biệt, tôi hỏi làm thế nào để "chuỗi" tốt hơn trong tương lai [Hoặc [A, B]] '. @tpolecat và những người khác đề nghị sử dụng Monad Transformers, cùng với một ví dụ hữu ích. –

Trả lời

2

Những thứ như Future[Either[AppError, Option[User]]] loại trả về có thể là ok tại thời điểm tạo mẫu nhưng một khi bạn đã hoàn thành việc tạo mẫu, bạn nên nghĩ đến các tùy chọn cung cấp khả năng đọc và thể hiện tốt hơn.

Cho phép lấy số Future[Either[AppError, Option[User]]] làm ví dụ. Giả sử có một phương thức có kiểu trả về này.

def fetchUser(userId: UUID): Future[Either[AppError, Option[User]]] 

Bây giờ, bạn có thể chọn để tạo ra một hệ thống phân cấp loại biểu cảm hơn ... ví dụ,

// Disclamer : 
//  this is just for pointing you out towards a direction and 
//  I am sure many can propose a better design hierarchy 

trait Model 
case class User(id: UUID,....) extends Model 

// Fetch Result protocol 

sealed trait FetchModelResult 

case class FetchModelSuccess(model: Model) extends FetchModelResult 

sealed trait FetchModelFailure extends FetchModelResult 

case class ModelNotFound extends FetchModelFailure 
... 
case class FetchModelGenericFailure(ex: Exception) extends FetchModelFailure 

// App Result protocol 

sealed trait AppResult 

case class AppResultSuccess[T](result: T) extends AppResult 

sealed trait AppResultFailure extends AppResult 

case class AppResultGenericFailure(ex: Exception) extends AppResultFailure 

// fetch user problem 

def fetchUser(userId: UUID): Future[FetchModelResult] = ??? 

// Notice that we are not using the generic AppError here 
// This is called segregation of problems 
// the current problem is fetching the user 
// so our design is just to represent what can happen while fetching 
// Now whichever method is using this can come-up with an AppError 
// or AppResult based on what is gets from here. 

def fetchUserApiHandler(userId: UUID): Future[AppResult] = 
    fetchUser(userId).map({ 
    case FetchModelSuccess(model) => ..... 
    case FetchModelFailure(ex) => .... 
    })  

Các tùy chọn khác sẽ được sử dụng thành phần và chuyển đổi các tiện ích monadic từ scalaz hoặc cats.

Raúl Raja Martínez đã giải quyết vấn đề tương tự và số cách để đối phó với những thuộc một trong các bài thuyết trình của mình - A team's journey over Scala's FP emerging patterns - Run Wild Run Free

5

Hãy có một cái nhìn tại các không gian giải pháp:

Success(Right(Some(user))) => Everythig OK, got an user 
Success(Right(None)) => Everything OK, no user 
Success(Left(AppError)) => Something went wrong at app level 
Failure(Exception) => Something went wrong 

này trông rất biểu cảm, nhưng mọi thứ trở nên xấu xí nhanh khi bạn cố gắng soạn cấu trúc lồng nhau như vậy với các cuộc gọi khác (xem Converting blocking code to using scala futures) để biết ví dụ về cách soạn Future[Option[T]])

Vì vậy, sau the principle of the least power, chúng tôi tự hỏi: Có những lựa chọn thay thế ít phức tạp hơn để bảo tồn ngữ nghĩa không? Người ta có thể lập luận rằng Future[User] có thể là đủ nếu chúng ta sử dụng toàn bộ tiềm năng của ngoại lệ (và phân cấp ngoại lệ).

Hãy kiểm tra:

Everythig OK, got an user => Success(user) 
Everything OK, no user => Failure(UserNotFoundException) (Application level exception) 
Something went wrong at app level => Failure(AppException) (Application level exception) 
Something went wrong => Failure(Exception) (System-level exception) 

Hạn chế duy nhất của phương pháp này là những người sử dụng các API sẽ cần phải nhận thức được các trường hợp ngoại lệ, mà không tự ghi nhận trong giao diện. Mặt trên là việc có API dựa trên Future s sẽ cho phép các sáng tác monadic biểu cảm với các API dựa trên Future khác.

+1

Tôi chắc chắn đồng ý về độ dài và API phân bổ được tạo bởi 'Tương lai [Hoặc [A, B]]'. Tuy nhiên, việc sử dụng ngoại lệ để mô tả người dùng không được tìm thấy? Đó có phải * thực sự là một trường hợp ngoại lệ *? Hay đây là một phần của quy trình kiểm soát thường xuyên của các yêu cầu kinh doanh? Tôi chắc chắn sẽ thận trọng khi sử dụng các ngoại lệ như một phương tiện để mô tả các luồng kinh doanh thông thường. –

+2

@YuvalItzchakov Khi được đưa trở lại ở cấp ứng dụng, 'Ngoại lệ chỉ là các lớp học với một đại diện xấu. :-) Tôi đồng ý rằng ADT là lựa chọn tốt. Điểm duy nhất của tôi là bắt đầu đơn giản và thêm độ phức tạp khi/khi cần. – maasg

+1

Tôi đồng ý rằng 'Ngoại lệ' là một lớp có đại diện xấu. Tôi không biết, nó chỉ không cảm thấy đúng với tôi để mô hình một cái gì đó dựa trên nó, những người không hoàn toàn hiểu được thiết kế có thể có được ý tưởng sai lầm. –

3

Nói chung, không có gì sai với API được đề xuất. Nó cung cấp cho bạn chính xác tính linh hoạt mà bạn cần, nhưng yêu cầu bạn phải viết một số lượng soạn sẵn để xử lý kiểu trả về, hoặc sử dụng scalaz/cats và biến đổi đơn điệu để trích xuất mọi thứ.

Nhưng, hãy để tôi thử và đề xuất một API bổ sung.

Hãy xác định đại số của chúng tôi (hoặc các kiểu dữ liệu trừu tượng):

// parten me for the terrible name 
sealed trait DomainEntity 
case class User(id: UserId) extends DomainEntity 
case object EmptyUser extends DomainEntity 

case class UserId(id: String) 

Thay vì mô hình hóa các phi tồn tại của một người dùng với một Option[A], chúng tôi sử dụng đại số của chúng tôi để xác định miền của chúng tôi.

Bây giờ, chúng ta có thể tiếp xúc với một Future[Try[DomainEntity]], mà chúng tôi sau này có thể phù hợp cho các kết hợp khác nhau được tạo ra bởi các API:

findUserById(UserId("id")).map { 
    case Success(user: User) => // Do stuff with user 
    case Success(EmptyUser) => // We have no user, do something else 
    case Failure(e) => // Log exception? 
} 
+1

@downvoter Tôi rất muốn biết lý do cho downvote. –

+1

Tôi tò mò muốn biết lý do bỏ phiếu xuống. – pedrofurla

Các vấn đề liên quan