API design for `connection` style resource


#1

Hey gents, I’m designing an interprocess thing for our application and I’ve got it working pretty well, but I’m wondering about the exact semantics of connections.

given the classes:

class SessionFactory {
  fun createSession(someConfig: Config): Session
}
class Session: Closeable {
  fun exchange(request: Message): /*response:*/ Message 
  //a couple other methods for fire-and-forget functionality
}

in point of fact, our actual API has a boatload of suspend's in it, but I dont think that’s relevant here.

My happy-path code can then look like

@UIFramework fun onSyncButtonClick(event: Event){
  val uiRelatedValues = sessionFactory.create(config).use { session -> 
     //suspending code block that hits this device a lot and builds useful information for this view.
  }
}

which is all very nifty, except I’m wondering what the best way to express an error here.

I could have the return type from SessionFactory.create() be a sealed class with one of those sealed classes being some kind of error wrapper. The idiom then becomes

class SessionFactory {
  fun createSession(someConfig: Config): SessionOrError
}
sealed class SessionOrError: Closeable {
  data class Error(val cause: ProblemDescription): SessionOrError {
    override fun close(){}
  }
  class Session: SessionOrError {
    fun exchange(request: Message): Message
    //...
  }
}

@UIFramework fun onSyncButtonClick(event: Event){
  val uiRelatedValues = sessionFactory.create(config).use { sessionOrError -> 
     when(sessionOrError){
       is Error -> doPopupOrLogOrSomethingClever(sessionOrError.error)
       is ValidSession -> {
         //previous code block
       }
     }
     //could also reduce indentation with 
     val session = if(sessionOrError is Error){
       doPopupOrLogOrSomethingClever(sessionOrError.error)
       return@use
     } else (sessionOrError as Session).session
  }
}

I could use funktional’s either type:

class SessionFactory {
  fun createSession(someConfig: Config): Either<Session, Error>
}
@UIFramework fun onSyncButtonClick(event: Event){
  val uiRelatedValues = sessionFactory.create(config).mapLeft { error ->
    doPopupOrLogOrSomethingClever(error)
  }
  uiRelatedValues.mapRight { it.use { session -> 
    //...
  }}
}

I suppose I could also up the arity and proivde a custom use method, such that we get something like

@UIFramework fun onSyncButtonClick(event: Event){
  val uiRelatedValues = sessionFactory.create(config).customUse { session, error -> 
     if(error != null){
       doPopup(error)
     }
     else if (session != null){
       //...
     }
  }
}

if you were a dev on my team, what would you like to see? what would you hate to see? How comfortable are you with something like an Either type?


#2

The first thing that comes to mind when you’re dealing with errors is good old exception. I would definitely use exceptions for error handling in this case.