Service architecture in Scala

Introduction

Today we are going to look at the Service architecture and specifically how to implement it in Scala but that would work in any object oriented languages.

What is Service architecture?

I first learned about it in this blog post from John A De Goes about ZIO.

The idea is to isolate external Services and being able to assemble them into a set of Services and give only the relevant part to the consumer.

How do you implement a Service?

In Scala, you will need trait and object.

trait Database {
    def getDatabase: Database.Service
}

object Database {
    trait Service {
        def query(query: String) = ???
    }

    object Live extends Database {
         def getDatabase: Database.Service = LiveService
    }

    object LiveService extends Service {
        // Real implementation
    }
}

That would be a Database service. As you can see, there are several components.

The core of the implementation is inside Database.LiveService this is where all the database connection, etc.. are going to be.

The Database.Service is the description of the API that the implementation must provide.

The Database trait is what the outside world will see. This architecture allow you to combine those trait later (see next part of this article). This is needed because you might be mixing together Database and FileIO and both of those Service might have a read method. And that won’t compile. Having this extra step where you first acquire the service and then use the methods protect you from that.

How do you use Services?

By composing the traits together! Let’s see how that works:

case class Universe(/* dependencies */) 
    extends Database.Live 
    with FileIO.Live 
    with Console.Live {
}

And then you can use it in the rest of your code:

case class ClientSources(services: FileIO with Console) {
    // implementation
    val content = services.getFileIO.readFile(path)
    // implementation
    services.getConsole.println("foo bar")
    // implementation
}

val universe = Universe(???)
val clientSource = ClientSource(universe)

And that way you have access to the services you need and you don’t have to give a million arguments to all your classes and methods.

Why would you want a Service Architecture?

To me, there are two main reasons.

First, testing ! The same you have the Live implementation, you can implement the Service for your test with fake data. That is similar to the mock libraries but all build-in your code.

Second, no need to pass too many arguments around. When the code base get too big, sometimes you end up with critical services that need to be given from class to class and your list of arguments is getting bigger and bigger. With this approach you only have one thing to pass around. If you really want to go for it, you can even have the services as implicit val but it can get tricky.

Practical Examples of Service architecture in Scala

This time around, the practical example is going to be a bit longer than usual. There is a bit of boiler plate with the Services, but hopefully that will give you a good sense or what it is.

You can open Scastie in your browser or use it directly here:

Conclusion

Let me know what you think about the Service architecture in the comment below and please don’t hesitate if you have any questions ! And you can join the conversation on Reddit.

2 thoughts on “Service architecture in Scala”

  1. Hi Leo,

    Thanks for writing this.
    I think the part of “assemble them into a set” is very useful and can help a lot with methods or classes that have too many arguments.

    But regarding the tests part, I am having a hard time seeing your use case. Why would you write test specific code?
    Why not use a mocking library like mockito? The tests that should not use the real “live” code version of a service should just mock out the needed methods of that service. And with a good mocking library like Mockito, that’s a one line effort.

    Cheers!

    Reply
    • I totally agree with you ! Thank you for the comments.

      To me, I had dangerous surprised when I would use Mockito to mock a little bit too much and I end up not testing exactly what I would need to test.
      For instance, mocking the tool that wrap the source service instead of the source itself and there is a bug in the parser.
      It can create dangerous surprise in production code. But you can definitely use both in combination !

      With this architecture, you can call the main pretty much as is in your unit test and execute 90% of your code base.

      But to me, this is more obvious when this is wrapped into the ZIO library. Used as is, it can be a lot of effort for a relative gain.

      Also, as a side note, for testing, I like using Stryker4s to validate that my test are actually testing anything !

      Reply

Leave a Reply

%d bloggers like this: