Domain specific types in a play framework project.

Domain specific types in a play framework project.

Domain specific types in a play framework project.

We can all agree that it’s useful to be able to express a number. Or a character. It could be argued that most code is built upon some set of primitives. And naturally simple types work well in a limited number of use cases: algorithms, tutorials, small/focused applications, etc.

The moment the domain outgrows the natural usage of a given primitive it becomes troublesome to keep in mind what the meaning behind a particular Int is. We all know this, it’s been the driving force behind OOP since day one.

Recently I’ve been working on an application based on play framework 2.4.x and slick 3.0. Unsurprisingly, as the app grew the need to track and control domain-specific data became more and more pressing.

Type aliases

It’s useful to be able to quickly start with something simple. Scala has type aliasing which provides a simplistic way of expressing intent.

https://gist.github.com/anonymous/8c05cc21c3a6604fa5686ee66c4198df

Quick & easy but has some drawbacks:

https://gist.github.com/anonymous/1215152bbe64ec2a83c0fcb723dde454

The above code compiles fine but Mike might not be all that happy about it. The solution itself isn’t that bad: gives no type safety and serves only as a hint but it’s very cheap and can easily be employed as a ‘stub’ for a better solution. It’s important to stress that this approach doesn’t scale well: domain specific aliases x amount of developers x features implemented equals a misassignment bound to happen.

Unboxed Tagged Types

After reading this post I’ve decided to try if this approach could work with playand slick. The gist of this approach is that it’s possible to attach ‘meta information’ to a primitive type and the typer will enforce it.

Implementation (scalaz):

https://gist.github.com/anonymous/1692bacf27c988816d8714fa31fc0388

In practice it looks like this:

https://gist.github.com/anonymous/0c2b723ae9d8b403a0a03e7a65cefac2

Mike can be at ease:

https://gist.github.com/anonymous/1819fd03c023874d60e1175e6c76fc30

produces:

https://gist.github.com/anonymous/a4e5ab36a131865eae46334e1f1ba34d

Having the basics covered I got to the interesting parts.

Helpful ‘Int’ extension:

https://gist.github.com/anonymous/a9bfc0130642b1d95d6abf58bea84990

Removes .asInstanceOf[A] invocations and converts primitives into domain types:

https://gist.github.com/anonymous/ee5a6d8d08e26c5d5cd42b7c01144cb5

Slick mapper

https://gist.github.com/anonymous/dbd42c4d253f86f4fadd89dd7fd52fa7

Usage in table definition:

https://gist.github.com/anonymous/28cb26a9de2fcd56b2e7e284f1dc708a

Play json formatting:

https://gist.github.com/anonymous/ea22d40badc2b9072667d9e6ba62ad71

Play routes

This is where the trouble starts. To be able to use custom types in routes a path binding needs to be provided:

https://gist.github.com/anonymous/8dbdf6de25218013bcd377d5d12a9829

routes file:

https://gist.github.com/anonymous/6f63a671098fc6fc392d8424437ab02a

And the error:

https://gist.github.com/anonymous/3b1a6b84e40bde70e2578cc6d1327974

It turns out that:

https://gist.github.com/anonymous/dc2e81d254c60778643c05395efcd2e3

is what play framework generates in Routes.scala.

The ramifications are quite annoying – instead of having type checking in routes file it’s necessary to handle boxing manually:

https://gist.github.com/anonymous/0d08c73d61f8e2b9f3de0b9c25a194a3

Controller:

https://gist.github.com/anonymous/c0c805f86e9c07d73e58ab91402427fc

In tests:

https://gist.github.com/anonymous/dd503efc09603dabffa5c44ff44fa116

It’s worth keeping in mind that unboxes tagged types and play jsondon’t align perfectly.

The proposed solution covers some of the aspects that would make it a valid solution for handling domain specific types. It falls short in a very important spot: ease of testing. The test code base should be (in my opinion) easy to maintain and refactor and loosing type safety in this area is a deal breaker.

At this point I was out of ideas on how to push this solution further so I decided to give a completely different approach a try.

Case classes

Case classes come with a set of limitations, but when the problem calls for type-safety, readability and ease of integration with common libraries (in this case play and slick), they quickly become valuable tools.

Value class with slick’s mapping

https://gist.github.com/anonymous/98d41b8fc0ebcb90314a1bfe0e6c95be

Json serialization

https://gist.github.com/anonymous/ac1aad86f5f62c86605d74eb9a9478f3

Int implicit extension

https://gist.github.com/anonymous/33daebd2b52c33ab3cb5128d5db9fad0

Play path binding

https://gist.github.com/anonymous/c09bb2fb956312eebc70a3525b5552f2

This way routes can utilize the type system:

https://gist.github.com/anonymous/fdccec9dcccdc9632785da3821a1edbd

and in tests:

https://gist.github.com/anonymous/4cc5747070b80b1db529385a2451ea0d

A lot of the code base in a DB centric application will call for values: names, identifiers, nicks, etc. Not all of these need to be processed and if not – the above approach makes building applications easier and less error prone.

Macros

The previous paragraph shows how to integrate case classes with the stack but it introduces boilerplate. It’s not a lot but it seems wasteful and might be error prone. Maybe we can use some macro magic to make it go away?

Here’s my take on the problem, heavily inspired by this article.

Annotation

https://gist.github.com/anonymous/9115e001dd9a2edcb8d19b67854bd451

annotation expansion

https://gist.github.com/anonymous/df7b4c091752a1e4599656ecef10c222

def macros

Providing def macros and utilizing them in the annotation macro made the json format visible for other already defined macros (e.g. Json.format). This way if another class uses one of the annotated case classes and defines an implicit Json.format the compiler won’t complain.

https://gist.github.com/anonymous/69ac6fed2f8cc649df4e6d1732a516ce

https://gist.github.com/anonymous/1e969d53144f4d40b2230123bd00d163

build.sbt macros section

One way to make the macro compilation order happy in a play application:

https://gist.github.com/anonymous/4a467af730ee08b0334153b28f1d4ce8

Usage

https://gist.github.com/anonymous/e0f829debf88dda8a791ee24d22fb8a6

This solution uses macro paradise so it’s quite volatile (read experimentalsubject to change etc.) but it makes adding new simple domain specific types very straightforward: path bindingsjson serialization and slick integration just work ™ for all primitive types.

One thing that was omitted from the macro is primitive type extension.

Summary

It would be so useful to be able to reason about a primitive and have the compiler police it’s domain constraints. Disappointing but understandable – library authors have a lot of interop requirements to appease and a niche feature that seems to be a tad outside of how scala programming should look like isn’t a priority.

Case classes may not be the perfect solution but the approach is popular enough for library authors to provide out-of-the-box helpers that just work (e.g. slick’s MappedTo). And if not: it’s always possible (and quite easy) to figure out a macro or two that will meddle with the case class itself or its companion object and deal with boilerplate around the required implicits.

Links

Do you like this post? Want to stay updated? Follow us on Twitter or subscribe to our Feed.

See also

Download e-book:

Scalac Case Study Book

Download now

Authors

Dominik Zajkowski

Latest Blogposts

23.04.2024 / By  Bartosz Budnik

Kalix tutorial: Building invoice application

Kalix app building.

Scala is well-known for its great functional scala libraries which enable the building of complex applications designed for streaming data or providing reliable solutions with effect systems. However, there are not that many solutions which we could call frameworks to provide every necessary tool and out-of-the box integrations with databases, message brokers, etc. In 2022, Kalix was […]

17.04.2024 / By  Michał Szajkowski

Mocking Libraries can be your doom

Test Automations

Test automation is great. Nowadays, it’s become a crucial part of basically any software development process. And at the unit test level it is often a necessity to mimic a foreign service or other dependencies you want to isolate from. So in such a case, using a mock library should be an obvious choice that […]

04.04.2024 / By  Aleksander Rainko

Scala 3 Data Transformation Library: ducktape 0.2.0.

Scala 3 Data Transformation Library: Ducktape 2.0

Introduction: Is ducktape still all duct tape under the hood? Or, why are macros so cool that I’m basically rewriting it for the third time? Before I go off talking about the insides of the library, let’s first touch base on what ducktape actually is, its Github page describes it as this: Automatic and customizable […]

software product development

Need a successful project?

Estimate project