1

Below are oversimplified examples, but I wonder which route to take what is the best practice when designing API, the simple or the more complex and robust which is better?

  1. This looks good and goes along with scala ADT concepts but I fear that it would become overly complex and the clients using my API will a little hate me for overcomplicating them with too many objects to think what to pass to my interface.

    trait ProcessUpdate {
      def name: String
      def id: String
      def message: String
    }
    
    case class SuccessProcessUpdate(name: String, id: String, message: String) extends ProcessUpdate
    case class FailureProcessUpdate(name: String, id: String, message: String, e: Exception) ProcessUpdate
    
    def success(successUpdate: SuccessProcessUpdate)
    def fail(failUpdate: FailureProcessUpdate)
    
  2. This is simple, I think the ones using this API will like me better, but not sure if it's robust enough.

    trait ProcessUpdate {
      def name: String
      def id: String
      def message: String
      def e: Option[Exception]
    } 
    
    case class ProcessUpdateInfo(name: String, id: String, message: String, e: Option[Exception])
    
    def update(processUpdate: ProcessUpdate) 
    

What do you think? is there a right answer? this example was oversimplified the more complex the thing the harder the question.

Jas
  • 507

1 Answers1

0

Without knowing any more about the purpose of your program or the use cases, I usually follow an implementation pattern for ADT like so:

sealed trait ProcessUpdate

final case class SuccessProcessUpdate(
  name: String, 
  id: String, 
  message: String
) extends ProcessUpdate

final case class FailureProcessUpdate(
  name: String, 
  id: String, 
  message: String, 
  exception: Exception
b) extends ProcessUpdate

def update(processUpdate: ProcessUpdate) = processUpdate match {
  case SuccessProcessUpdate(name, id, message) => // do something
  case FailureProcessUpdate(name, id, message, exception) => // do something else
}

You could specify fields in ProcessUpdate, but by using sealed traits, you don't really need to specify that in the trait, and each case class that extends ProcessUpdate can look completely different. You can deal with each specific implementation by using pattern matching as shown above. If you don't need as much complexity, then I would forgo the trait entirely and implement it like so:

final case class ProcessUpdate(
  name: String, 
  id: String, 
  message: String, 
  exception: Option[Exception] = None // use default to allow shorter syntax
)

def update(processUpdate: ProcessUpdate) = processUpdate.exception.fold {
  // success case
} { exception =>
  // failure case
}
Samuel
  • 9,237