Friday, September 27, 2019

Map or any other option?

I must admit that sometimes we can go too far with using Optionals as silver bullet. Let's take a look at a simple mapper class, defined like this:
class Mapper {
  Optional<Dto> map(Optional<Entity> input) {
    //... implementation
  } 
}
At first passing an Optional instance looks like a good idea - we want to be safe from null values, we express our defensive intent in types. What can go wrong? Let's now take a look at how this mapper may be used.
Entity entity = //...
Dto dto = mapper.map(Optional.of(entity)).get();
Well, typical code involving the mapper is packing the input entity into an instance of Optional, just to get to output value. Even worse could look usage for a collection.
List ents = //...
List<dto> dtos = ents.stream()
  .map(Optional::of)
  .map( x -> mapper.map(x) )
  .map(Optional::get)
  .collect(toList());
Instead of using type system and compiler to our benefit, we lie about our input value - which we actually know that is never null. Not only we do not use the knowledge we already have, but put extra burden of reading through the wrapping on reader's eyes. I do not mention creating extra instances of Optional, and making common use of calling .get() - which is not best practice. I do not want to get familiar to that.
What I also do not like in the code is forcing the user of our mapper to provide object wrapped into the very implementation of Optional. Actually mapper should not care which implementation of Optional I use. There are more than the standard one, and they may have some properties that are more useful for me in the context of my implementation. Last thing, we may observe here is that the API actually mimics what the Optionals are expected to provide - the map method itself.
What we need is just:
Dto map(Entity in) {
//....
}
That simple.
Implementation of very mapping is what should be provided directly by mapper, without requirement for a wrapped value. We should require that value will be non-null. Optionality should be handled in client code somewhere else, we will not tolerate null values anyhow, right? Such mapper can be also easily used when applied to streams or collections. We save machine's memory from wrapping non-null values into Optionals and our precious eyes from reading the code that would cause the latter.

Friday, July 19, 2019

Woobie Doobie - switching database access library in Scala

Doobie is getting more and more popular as a database access tool in modern applications. This year on Scalar conference voting Doobie won over its competition - particularly Slick. Not without a reason. Recently my project also dropped Slick.
Doobie gave us nearly immediately:
  • easier learning curve - it is about plain, old SQL ;
  • more compile time verification - now you do not need to run your query to find out your type mapping is broken ;
  • referential transparency and better integration with effects system used in project ;
  • no hacking needed when you need to select more columns than unfamous 22. One particular issue that came out quite early was a bit tricky to resolve with given message:
    [error] Cannot construct a parameter vector of the following type:
    [error]
    [error]   Boolean(true) :: shapeless.HNil
    [error]
    [error] Because one or more types therein (disregarding HNil) does not have a Put
    [error] instance in scope. Try them one by one in the REPL or in your code:
    [error]
    [error]   scala> Put[Foo]
    [error]
    [error] and find the one that has no instance, then construct one as needed. Refer to
    [error] Chapter 12 of the book of doobie for more information.
    
    Studying chapter 12 did not help. Here is the query that caused the issue:
    sql"select one_col, two_col from TABLE where flag = ${true} "
    
    It turns out that Doobie (actually Shapeless) does not handle properly the inlined true literal. What is needed is just to extract it to separate value:
    val flag = true
    sql"select one_col, two_col from TABLE where flag = ${flag} "
    
    That resolves the problem. It would also work if we added type annotation:
    sql"select one_col, two_col from TABLE where flag = ${true: Boolean} "
    
    It's up to you which way you prefer.