Beware the Quick Start Panacea, and the Importance of Layers

Brian Lawler
That’s What I’m Talking About
8 min readJul 19, 2016

Any web framework that you come across these days will try to entice to use it by convincing you that you can get an application “up and running in 10 minutes!!” While I appreciate the desire to jump in and start getting things done, I also fear that the 10 minute quick start can get you off on the wrong foot from an architectural perspective. The Quick Start is a valuable tool to get you started with the framework and verify that it may in fact make your life easier. It provides knowledge of how to build an application using that framework. This is not the same thing as knowing, in general, how to build an application. If you are building an application that will need to grow and change, you’ll want to take a more layered approach to building it.

I have been working on a couple of projects for the last several years where this layering has proven to be a great tool for adding new functionality and for explaining the system to new engineers joining the project. The approach I’ve been slowly perfecting is a lightweight form of Domain Driven Design, but without all the heavyweight terminology. My language(s) of choice for back-end development are Scala and Java, and the framework I have been using is Play. My applications are generally broken up into a few different layers which I’ll cover in detail in other posts, but today I want to start at the beginning, with the domain layer.

Start with the Domain

I have come to love coding in Scala over the years for many reasons, but I still use Java interfaces to define my domain. By domain here, I am just talking about the various objects that make up a system, the attributes that those objects possess, and the interactions that are possible among them. Domain modeling is nothing new, and there are lots of resources online that describe it. There are tools and methodologies people use to model their domain, but I find that using Java interfaces to define things at this level is preferable for a few reasons:

  1. Interfaces can’t contain implementation. It’s critical to not worry about implementation when you are defining your domain. In this layer you just define what your application does, not how your application does it.
  2. In the most lightweight manner possible, interfaces will introduce type safety to our system, and if done properly you will be able to rely on the compiler to help insure the correctness of your code.
  3. You can’t instantiate interfaces directly — you must instead create classes that implement those interfaces. This means that the domain-level definitions won’t have any opinions where the domain objects come from, only that they follow the rules put forth by the interface contracts.

So rather than diving right in to creating HTTP controllers, data models, and endpoints I prefer to think first about the various types of domain objects that my application will need to support, and I think of those in terms of Java interfaces.

Pirc.com as Sample Application

I will use as an example one of the sites that I have been working on, Pirc.com. Pirc is a shopping application whose goal is to bring the weekly sales circular that you used to find in the Sunday newspaper into the 21st century. With Pirc, we allow our users to build their own personalized circular (which we call your Pircular) featuring their own favorite brands at their favorite stores. We programmatically ingest all of the online versions of these circulars every week, per store, per zip code. Then we programmatically do “coupon matchups” where we search various coupon sources to see if any of the sales also have coupon matchups, which is a process that many people still do manually today. Then, we send each user a customized email every Sunday containing the sales that we found on the products that they have specified. Pirc will be the example application that I use in many That’s What I’m Talking About (a.k.a. TWITA) articles and there is also a github repository for the Pirc rebuild that will be a companion to this publication.

A Simple Start, with an Immutable Twist

So let’s jump in with a first example of what I’m talking about with this “domain-first” approach, with respect to Pirc. Let’s begin to build up some requirements for the Pirc application, in plain English first then translate that into a more Java-friendly format.

REQUIREMENT: Our application needs to have the notion of a User.Users are where it all starts — I can’t gather your favorites without somewhere to put them all. A User will have a first name, a last name, an email address, and a password. This is a pretty simple starting point, and the User object will obviously grow in the future. Let’s express this User in source code:

Before we go on, there is one thing that I’d like to point out about this simple start: there are no “setter” methods. If you have this User interface in hand (we’ll later go into how you would get one), you can’t actually change any of the fields on it. It is immutable. This will become an important detail as we continue to build out the other software layers.

With a User interface contract defined, the next question might be “what do I do with it?” At this point, not much. But we know that we’ll have to create new users in the system, and that those users will be able to do much more than just have a name, email, and password. We could skip straight to the part where build up the implmentation of User, but then I’d be jumping layers on you!

Registering New Users

Instead, let’s talk about how to register a new User in our system, or rather, let’s lay out another requirement:

REQUIREMENT: Users must be able to register with the system.

Don’t be tempted to get imperative here! We aren’t yet concerned with how registration works, only that we can do it. This means that somewhere in our system we need to have an interface definition for that. With Pirc, the registration API lives in something called the UserDirectory, which is an object that will be used to create and retrieve Users, among other things.

I have added a couple of other methods to this UserDirectory interface to give this class a bit more substance. For the purposes of our registration requirement, it is the createUser() method that is important. Notice, however, that the createUser() method itself requires a User instance on the input side. Huh? I need a User to create a User? Remember that User is just an interface. We haven’t yet built any implementations. All that createUser(User tplt) means is that the application that is using the UserDirectory object to make new users in the system will need to pass in something that implements User in order for the compilation process to succeed. In coming posts, we will see the value of these template objects as a way to help you find errors in the various layers of your application as the domain objects themselves grow and change. This will be an important topic as we move through the process of building Pirc domain-first.

The “Root” of the Whole Application

So now I have a User object, which is somehow obtained from a UserDirectory object. But where to I get the UserDirectory from? I thought I would wrap up this post by tying up that loose end. What we are going to see as we build out the Java interfaces that make up this API is that there are lots of interactions that make sense as members of our appliation objects (e.g. User.changePassword(“new password”) ) and there are other functionalities that won’t have an obvious home within a particular domain object. Where, for example, do I get a UserDirectory from?

Enter the “Application” object, the root of the whole thing. We’ll still need to get an instance of this thing somehow (teaser: this will be done using some form of dependency injection) but once we have an instance of our root Application object, then we will be able to use it to do anything else that our application is capable of doing.

So once we have an instance of PircApi, we can now create a new User using the UserDirectory object which we obtained from our root API object. Of course you can’t really do that yet because we haven’t yet implemented any of that. But what we have done is specified the interface contracts that will ultimately provide this functionality. When we get there, registering a new user will look like this:

API.getUserDiretory().createUser(userBinder);

Where “API” will be an instance of our PircApi object and “userBinder” will be the template instance of our User object. There will be implementation details to fill in (that’s when we get imperative), but for now in defining our interfaces we have taken the declarative route — explaining what it is that we need to be able to do rather than how to do it.

Conclusion

So to bring this back around to the original topic, another way to look at the Quick Start Panacea is to say that it jumps straight to the imperative (how to use the framework) without first laying out the declarative (what we need the application to do). The purpose of the Quick Start is, after all, to tell you how to do things; how do I define HTTP endpoints, how do I invoke controllers on my server, how do I store data in the database? But my point is that while this may be a good way to show how much stuff the framework is taking care of for you by illustrating how little you have to do to get things going, it’s a bad way to code an application. It is in fact possible to lay out (and compile) your entire domain before you have even chosen a framework, or decided whether your application is a desktop app, a mobile app, or a web app!

Putting too much code into your controllers unnecessarily binds your domain to being run within the framework that provides those controllers. What you will see as we continue to build out the Pirc API is that the API layer we have just begun working on will have almost no external dependencies. It won’t depend on any Play libraries, nor will it depend on the Java Servlet API. This will keep the API completely “execution context agnostic.” It may end up a simple web application, it may end up as a cluster of micro services, and there may be parts of it that get deployed as stand-alone background processes running in completely different environments. In essense, we are doing a little future-proofing of our app without spending a whole lot of time on needless optimization. We aren’t yet creating end user value, as no one is yet able to register for our web site, but we are creating a compiled, statically checked documentation for what our system is capable of doing. It’s not a Quick Start, but it’s a solid one.

Next time, we’ll continue to build out our domain and get a bit more into the value of thinking domain-first.

--

--