Scala is a powerful language. You are given a lot of features allowing to realize your applications in various ways.

In the Java world, there is a history of using frameworks and platforms. They define and force a lot of behaviors. For example Java EE or Spring define how to configure your application, what is the object life-cycle and many other things.

In Scala, you tend to see smaller libraries, which are quite flexible. That is why many things like application structure and life-cycle fully depend on you. But remember, with great power comes great responsibility.

This blogpost will focus on the application organization and structure. In the second part will focus on the application flow.

Structure

During work on the application, you have to decide on a few code related things.

Object creation style

First topic to decide is how do you create objects. Manually (using new ), or using some Dependency Injection (DI) library. From Java, we are used to using DI everywhere in every project. In Scala it's different. The topic of DI in Scala was already broadly covered by Adam Warski in one of his articles.

Let’s say that we’ll be using pure Scala with new keyword. How do you compose modules of your application? Let’s say that we’re building an application for Sumo wrestlers. We would need features related to e.g. diets, training plans and schedules. They would create separate packages, each of them containing separate module. The question is, how to compose them into the Main of your application?

Modules as traits

The first option is to treat your application modules as traits. Then the Main just need to extend all other modules from your application. What when one module needs an object from another one? Then, you need to add a field in a trait whose value will be overridden.

If you added self-types to the following example, then you’d have already a cake-pattern.

Modules as classes

Another way is to model modules just as classes. Then the Main includes other modules as fields. If there is any dependency between modules, then they are just passed via modules constructors.

We have used both approaches in various projects and so far I prefer the second one. It makes tests cleaner, especially around providing modules dependencies.

Package organization

Secondly, what would be the package organization? By layer, or by feature? I personally prefer the organization by feature. When you are writing code, everything related to the feature is located in the same parent package. You don’t need to jump over whole service, all needed is together. Inside of the feature package, if you need, you may have of course layer packages. So what happens on what layer? For example:

com.softwaremill.sumo.diet — module parent package, feature related to diets. This package would contain a module definition, e.g. DietModule . Inside of the Module we would see creation of various objects from this package

class DietModule(database: Database)(implicit executionContext: ExecutionContext) {



lazy val dietRepository = new DietRepository(database)

lazy val dietService = new DietService(dietRepository)

lazy val dietController = new DietController(dietService)

}

com.softwaremill.sumo.diet.domain — definition of domain classes, depending on case there can be multiple files inside

— definition of domain classes, depending on case there can be multiple files inside com.softwaremill.sumo.diet.rest or .api -REST APIs, *Controller or *Routes classes + e.g. Protocol . Their role is to provide REST API definition, request and response formats together with converters from domain classes and json-library dependent schema definitions. *Controller s may only contain logic related to API representation and appropriate conversions.

When you separate API, domain and database models, copying data between the representations can be quite tiresome. Take a look at the Chimney library which can make this easier for you.

com.softwaremill.sumo.diet.persistence - logic related to databases, *DAO or *Repository . In this case should return domain types.

- logic related to databases, or . In this case should return types. com.softwaremill.sumo.diet.application — application logic, *Service or *Process or *Program . Logic which would be called from the API level. This level should operate only on domain classes, but may integrate executions of Repositories from persistence or other external services (e.g. HTTP) clients, also from other feature packages.

Depending on your needs you may have more layers. E.g. you may be using external services client libraries, wrapped in another layer, so you operate only on the domain in your application logic.

Tests

Basic unit tests can be located in src/test in corresponding packages. Test classes can be named with a basic *Spec suffix. It’s worth to keep unit tests fast. If you have any non-public logic, which would be worth to put under test, then it can be put under package private, e.g. private[application] so that it can be accessed in the corresponding *Spec . If you have any heavier tests, like ones depending on databases, its worth to put them in separate directory, e.g. src/it , so that they can be run independently.

Maintaining order

The most important question is, how to keep order in your project? Initially, it may be solidly designed, but over time some mess may appear. How to avoid cycles between packages or enforce checks not allowing to use feature internal classes by another feature? There comes the ArchUnit with its DSL allowing to define all those checks in tests.

Andrzej Ludwikowski in his blogpost “Keep your domain clean in Event Sourcing” provided example of the ArchUnit check, which can validate dependencies between domain and other packages.

Conclusion

Scala usage may give you more power but you have to be careful not to introduce mess in your project. You shouldn’t be afraid of using the good old new keyword for instantiating dependencies. DI framework may not be really needed in your application.

In the next blogpost we’ll focus on the application lifecycle, including initialization and resource management.