Play framework modularization: everything you wish you had been told

There are many tutorials and articles describing basic module functionality and how you can build it. These articles sometimes leave blanks of the needed information, so this post will do an in-depth tour of the topic applied in the Play framework.

Like modules on top of each other in play framework

What is a module?

In Play framework the modules are small functionality packages. These can be easily attached to some project, encapsulating specific functionality.This makes them reusable, but this is why these need more careful design when writing.
If you are familiar with the clean code principles you know exactly why these are good for us (smile)

If you want to build a new module your objective will fall to two categories;

  1. you want to build some separate functionality like a function library, or
  2. you want to build your webapp from shards, and you want a subpage like module.

The library functionality can be separated into two more parts,

  1. you want to build a basic library or
  2. you want to build something that needs to onStart and onStop; we call them plugins.

 

How to start?

First of all you need a folder where you can create your module, and a sample “how to use” play app to test it. I will use <module> and <sample> as space-holders below to clarify things. I ‘m expecting you to have the activator in your path, and you to have basic knowledge of using a terminal. (I’m using windows, with a cmder terminal emulator, it is possible your terminal will not work out of the box with these commands.) I will use play java in the codes below, but the scala version is nearly the same.

create the modul and sample
> mkdir <module>
> cd <module>
> activator new <module>
  play-java-2.3
> activator new <sample>
  play-java-2.3

If you want scala or 2.4 modules simply write play-scala or play-java or play-scala-2.3 instead of play-java-2.3 .

 

First of all, we extend our module’s build.sbt:

add to build.sbt
organization := "my.org"
organizationName := "My Org Name"
organizationHomepage := Some(new URL("http://my.org"))

It is a custom to mark the writer/organization of the modules.

 

Secondly, we configure our sample’s build.sbt:

add to build.sbt
lazy val module = RootProject(file("../<module>"))
lazy val root = (project in file(".")).enablePlugins(PlayJava).dependsOn(module)

If you look carefully, you will notice that this must be inserted somewhere on the top of the file, because a root is defined by default. The first line is defining a new variable with the root of your module, and the second injects it with the dependsOn(module) call. (Yes, you do not need to insert the second line, it’s enough just paste the dependsOn after it.)

 

Now you can start your favourite IDE and start the actual programming. \o/

 

Notes for before you start coding

There are some principles to which you should pay attention. First of all, this module you start writing must be easily usable and it must be totally independent. Try to avoid connecting to databases, using models (which are defined by you) from the host application. Instead, try defining traits or interfaces. Let the user implement the save functions, and don’t try to force him to use your preferred db engine or name conventions. The less dependencies kept in your module, the better. Try to lay how you want to use this module down, and design your module with this very mindset.

IDEs can be your best friends and worst enemies too. I’m using IntelliJ. It has some good points, but can make really silly errors when you try to use it with sbt. I’ll try to summarize how to start a fresh project in IntelliJ after you crated it with the activator.

  • you only need to open the sample project (it is depends on the module)
  • go through the open wizard (use auto import, download sbt sources)
  • on the right, open the sbt tab and hit the refresh
  • build it
  • at this point there is a chance that everything is working as excepted, but most of the time that’s not the case (sad) it’s because of the RootProject definition of the module package
  • File > Project Structure > Modules > Add Content Root  can fix this problem

After that you have 2 projects in the project tree. Now we are ready to start coding!

 

Basic Library type modules

With these types of modules you don’t have so much to do.

First of all, you need to clean it:

clean module folder
> cd <module>
> rm -Rf conf/* public/* test/* app/controllers app/views

 

And write some code!

For example:

just write it
1
2
3
4
5
6
7
8
9
10
11
12
package src;
import play.Logger;
import play.Play;
public class WriterTest {
   public static void writeToLog() {
      String prop = Play.application().configuration().getString("test.str");
      prop = ((prop == null) ? "nothing interesting in the properties :(" : prop);
      Logger.info(prop);
   }
}

In the sample application you can call it easily. That’s all!

If you read this code carefully you can see, that we are reading the sample application properties file in there. So basically there are no limitations, all your static libraries can easily be separated from your code to modules. This is how you can make them more reusable.

 

Plugin type modules

So in play framework 2.3 the official documentation used this term for modules that have states besides your webapp. Versions 2.3 and 2.4 are different in this section, so I will post my base logic here, and then show how you can make it work on 2.3 and 2.4. The scala and java versions are still close enough to show only the less natural (java) way, it’s easy to port to scala.
So we will do a basic exchange rate downloader.

My first class is do the download and parse things:

Exchanger.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class Exchanger {
   private String base;
   private String target;
   private List<Double> history;
   public void doUpdate() {
      AsyncHttpClientConfig conf = new AsyncHttpClientConfig.Builder().build();
      WSClient client = new NingWSClient(conf);
      WSResponse response = client.url("https://www.google.com/finance/converter?a=1&from=" + base + "&to=" + target).get().get(3000, TimeUnit.MILLISECONDS);
      String ratestr = (((response.getBody()).split("bld>"))[1]).split(target)[0];
      double rate = Double.parseDouble(ratestr);
      history.add(rate);
   }
   public List<Double> getHistory() {
      return history;
   }
   public Exchanger(String base, String target) {
      this.base = base;
      this.target = target;
      this.history = new ArrayList<Double>();
   }
}

It is going to the googleconverter, and save the current exchange rate to a list.

We need a runnable to schedule it:

RefresherJob.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class RefresherJob implements Runnable {
   private final Exchanger exchanger;
   public RefresherJob(Exchanger exchanger) {
      super();
      this.exchanger = exchanger;
   }
   @Override
   public void run() {
      try {
         exchanger.doUpdate();
      } catch(Exception e) {
         Logger.error("There is a problem with the currencies!", e);
      }
   }
}

We need some more logic to initialize and start a scheduler:

ExchangerPlugin.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class ExchangerPlugin {
   private final static long refreshInterval = 300000L;
   private static Exchanger exchanger;
   public static List<Double> getHistory() {
      if(exchanger != null)
         return exchanger.getHistory();
      else
         return new ArrayList<>();
   }
   private void onStart() {
      exchanger = new Exchanger("USD", "EUR");
      exchanger.doUpdate();
      RefresherJob job = new RefresherJob(exchanger);
      Akka.system().scheduler().schedule(
            FiniteDuration.create(0, TimeUnit.MILLISECONDS),
            FiniteDuration.create(this.refreshInterval, TimeUnit.MILLISECONDS),
            job,
            Akka.system().dispatcher()
      );
   }
}

This is basicly our business logic. Now we need to wrap it up!

 

Play framework 2.3

In 2.3

  • we need to make the ExchangerPlugin extend from play.Plugin,
  • need to add an Override annotation to the onStart(), and
  • add a new constructor with one Application type parameter (see below).
modified ExchangerPlugin.java
1
2
3
4
5
6
7
8
9
10
public class ExchangerPlugin extends Plugin {
    ...
    public ExchangerPlugin(Application application) {
    }
    ...
    @Override
    public void onStart() {
        ...
    }
}

 

After these modifications, we can use it in a sample project. Add a new line to your conf/play.plugins (create a file if it doesn’t exist) with a number:namespace.class format. In our example:

conf/play.plugins
10000:src.ExchangerPlugin

The number is defining the load order of the plugins. (That is the main reason why this method gets the deprecated flag.)

 

Play framework 2.4

In 2.4 we have more things to do…

We need to make a new class (ExchangerModule):

ExchangerModule.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import play.api.Configuration;
import play.api.Environment;
import play.api.inject.Binding;
import play.api.inject.Module;
import scala.collection.Seq;
 
public class ExchangerModule extends Module {
      public Seq<Binding<?>> bindings(Environment environment, Configuration configuration) {
         return seq(
               bind(IExchangerPlugin.class).to(ExchangerPlugin.class)
         );
      }
}

 

We need a new empty interface (You can skip this step but I think it’s the proper way due to the documentation. For a non Interface example see this):

IExchangerPlugin
1
2
public interface IExchangerPlugin {
}

 

And we need to modify our ExchangerPlugin:

modified ExchangerPlugin.java
@Singleton
public class ExchangerPlugin implements IExchangerPlugin {
    ...
    @Inject
    public ExchangerPlugin(ApplicationLifecycle lifecycle) {
        onStart();
        lifecycle.addStopHook(() -> {
            // previous contents of Plugin.onStop
            return F.Promise.pure(null);
        });
    }
}

 

And if you want to use it in your project you need to add it to your application.conf like this:

application.conf
play.modules.enabled += "src.ExchangerModule"

 

And inject somewhere to your Controllers:

example to inject
@Inject
ExchangerPlugin exchanger;

For more information on why it’s changed between versions, see the offical Module/Plugin migration guide.

 

SubPage modules and applications

For this we will make a new structure (It is theoretically possible to make fully separated subpage modules work together, but it’s more common to use subpage modules only in one root application.):

our subpage configuration
multi-module-project
    |- app
    |- conf
    |- modules
    |   |-module1
    |   |-module2
    |- project
    ...

 

build.sbt-s:

mullti-module-project/build.sbt
lazy val root = (project in file(".")).enablePlugins(PlayJava)
  .dependsOn(module1,module2)
  .aggregate(module1,module2)
 
lazy val module1= project.in(file("modules/module1"))
  .enablePlugins(PlayJava)
 
lazy val module2= project.in(file("modules/module2"))
  .enablePlugins(PlayJava)

In the modules build.sbt just delete (or comment out) the lazy val root line.

routes:

multi-module-project/conf/routes
# Home page
GET        /                    controllers.Application.index()
->         /module1              module1.Routes
->         /module2              module2.Routes
# Map static resources from the /public folder to the /assets URL path
GET        /assets/*file        controllers.Assets.at(path="/public", file)
module1/conf/module1.routes
# Home page
GET        /                    controllers.module1.Application.index()
# Map static resources from the /public folder to the /assets URL path
GET        /assets/*file        controllers.module1.Assets.asset(path="/public", file)

 

Controllers:

At your modules, you need to use other namespaces! So if you want special module scope assets, you need to define a new asset builder, too . There are some annoying bugs with the namespaces + default asset builder so you need to make your module a dependent asset handler.

Assets.java
1
2
3
4
5
6
7
public class Assets extends Controller {
   public static controllers.AssetsBuilder delegate = new controllers.AssetsBuilder();
   public static Action<AnyContent> asset(String path, String file) {
      return delegate.at(path, file, false);
   }
}

You could use it in routes like above in the module1.routes example, and can use it in views like below in the view examples (smile)

 

Views in modules:

views example
<script src="@controllers.module1.routes.Assets.asset("javascripts/hello.js")" type="text/javascript"></script>

 

There are some drawbacks with this type of a module. First of all it’s not documented well enough; you need to do your personal research (possibly, we will create a more in-depth submodule blog post because of this). Secondly, they are more like logical bricks that can separate your huge codebase, and less like some lego pieces which are pluggable to nearly every spaceship you made from lego previously.

Offical subpage reference.

 

How to publish?

If you made the best plugin or library like module, you want to show it to others too – so how can you publish it?

Publish your module to locale first (from the module dir):

publish command consolte
> activator clean publish-local

You will see something like this on your output:

output
[info] Done packaging.
[info]  published module_2.11 to C:\Users\<user>\.ivy2\local\module\module_2.11\1.0-SNAPSHOT\poms\module_2.11.pom
[info]  published module_2.11 to C:\Users\<user>\.ivy2\local\module\module_2.11\1.0-SNAPSHOT\jars\module_2.11.jar
[info]  published module_2.11 to C:\Users\<user>\.ivy2\local\module\module_2.11\1.0-SNAPSHOT\srcs\module_2.11-sources.jar
[info]  published module_2.11 to C:\Users\<user>\.ivy2\local\module\module_2.11\1.0-SNAPSHOT\docs\module_2.11-javadoc.jar
[info]  published ivy to C:\Users\<user>\.ivy2\local\module\module_2.11\1.0-SNAPSHOT\ivys\ivy.xml
[success] Total time: 5 s, completed 03-Feb-2016 11:04:53

You can try it out with the sample application:

build.sbt modification
version := "1.0-SNAPSHOT"
-lazy val module = RootProject(file("../module"))
-lazy val root = (project in file(".")).enablePlugins(PlayJava).dependsOn(module)
+
+resolvers += "Local Play Repository" at "file://C:/Users/<user>/.ivy2/local"
+
+lazy val root = (project in file(".")).enablePlugins(PlayJava)
 scalaVersion := "2.11.6"
 libraryDependencies ++= Seq(
+       "module" % "module_2.11" % "1.0-SNAPSHOT",
        javaJdbc,
        cache,
        javaWs

If you want to publish it for a larger user base try this.

Have fun!

Gergő Törcsvári

Gergő Törcsvári

Software Developer at Wanari
I would love to change the world, but they won’t give me the source code (yet).

  • Luqman Ghani

    is cleaning project (rm -Rf conf/* public/* test/* app/controllers app/views) required for plugin type modules too? I believe it is mentioned in other posts about modules too.

    • Gergő Törcsvári

      It’s not required, just recommended. Most of the time you will never make a view in your plugin type module, so it can be deleted (like the controllers or the pregenerated configs or tests).

  • Luis Dipotet

    Have you try custom Twirl template in multi module project ? It is an inconvenience of Multi module project. Consider your multi-module project without your app folder at root level. Any solution for that?