loading...
Cover image for Creating a Domain Model rapidly with Java and Spring Boot

Creating a Domain Model rapidly with Java and Spring Boot

colaru profile image Cristian Olaru ・10 min read

Overview

In this short article, we are creating a domain model for a simple application. We illustrate how we are creating the classes/objects of the model, based on the application business description. We insist just on the entities that are part of the model.

Here we talk about domain modelling in the case of a Web application with a client-server architecture where we have Java on the server-side (backend). All the examples are in Java language. We use Spring Boot to accelerate the application startup.

Start the implementation

Tools we are using:

If you are on Windows OS then the Java JDK 8 can be installed manually and also Maven can be installed manually. Spring Initializr -https://start.spring.io can be used to download the Spring Boot starter app. But on MAC OS and Linux, the fastest way is to use the terminal commands as you can see after.

Starting our app (we name it cindykat - a name very close to syndicate) and running it is is just like this:

# install SDKMAN
curl -s "https://get.sdkman.io" | bash
# install JDK
sdk install 8.0.212.j9-adpt
# create app
curl https://start.spring.io/starter.zip -d name=CindyKat -d groupId=com.colaru -d artifactIf=cindykat -d packageName=com.colaru.cindykat -d dependencies=web -d javaVersion=8 -o cindykat-springboot.zip
# unzip, then run it
unzip cindykat-springboot.zip -d cindykat-springboot
cd cindykat-springboot && ./mvnw spring-boot:run

Now our app is up and running on 8080 - it is a web application! No need for a web app - is ok to have something up and running.

We have this project structure:

What is the application domain?

The domain model is the place in our code, where we are modelling the business of our app. Usually, an application model has to be a direct reflection of the business it implements. Our code has to be a story of the reality we are modelling. And we have to use a ubiquitous language used by all people working on the project.

That's no need to introduce other abstractions different than what we have in the reality we are modelling. The nouns and verbs from the business have to appear in the name of our classes, fields, methods. When we introduce other things - we can be suspected of over-engineering.

Domain model
Image source: https://www.slideshare.net/Dennis_Traub/dotnetcologne2013-ddd

Why is the model so important? Because all the rest of the application will be in contact with the model. It is hard to have separate packages of DTOs or WS REST resources or persistence entities because of the marshalling/un-marshalling complications. We will be forced to do this if we don't want to expose the internal model outside the system. So we will use the model in all the layers of the application. You can be sure that the UI, persistence, reporting, WS, messaging integrations, all are using the domain model classes.

And the model can become more an more complicated over time (hundreds of classes). And what is in the model will evolve and will influence the entire application. This is the hardest part to be understood in any app by a new development team joiner. Here is not something like a library or framework reused in other applications, but here is something specific to a business. And the businesses can be very, very complicated.

The DDD book

There are a few ways of starting a Java Web application:

  • Backend first - play with the Java domain using tests to create a domain model that is the core of the server-side
  • Frontend first - start with UX/UI with HTML/ CSS/ JavaScript and create some mockups that will illustrate the user interaction - you can play with a domain model in JSON or Typescript here also

It is common to have a team that is working in the frontend, and a different team that is working in the backend and the contract between them is a Swagger/Open API spec.

The first approach, modelling the domain, is the subject of this article. And is the subject also of a very well known book: Domain-Driven Design by Eric Evans. In this book, more topics are introduced apart from the graph entities described in this article.

Image from Domain Driven Design Quickly book

Our application business explained

The application we want to implement is an analytics system for the data provided by Google Trending Searches - The history of searches from Google Search reported per day and country by Google. We want to import some data from Google Trends, then store it in our system and make it possible to analyse it, show it in a different form, etc.

In this case, it will be simple to model our domain used in our analytics system. Because we are distingue our entities from the format used to get the searches data. See this Google Trends Atom Syndication Format XML snippet:

        <item>
            <title>Women's World Cup 2019</title>
            <ht:approx_traffic>1,000,000+</ht:approx_traffic>
            <description>2019 Women, Women World Cup, WWC</description>
            <link>https://trends.google.com/trends/trendingsearches/daily?geo=US#Women's%20World%20Cup%202019</link>
            <pubDate>Mon, 10 Jun 2019 22:00:00 -0700</pubDate>
            <ht:picture>https://t2.gstatic.com/images?q=tbn:ANd9GcTW4UzPHNC9qjHRxBr6kCUEns71l8XK6HYcmLpJbhlfZWUbeBQPiia1GDzN3Ehl7nfD-HPbgnG_</ht:picture>
            <ht:picture_source>CBSSports.com</ht:picture_source>
            <ht:news_item>
                <ht:news_item_title>&lt;b&gt;2019 Women&amp;#39;s World Cup&lt;/b&gt; scores, highlights: Canada squeaks by, Japan underwhelms, Argentina gets historic point</ht:news_item_title>
                <ht:news_item_snippet>Day 4 of the &lt;b&gt;2019&lt;/b&gt; FIFA &lt;b&gt;Women&amp;#39;s World Cup&lt;/b&gt; in France featured a two-game slate with two potential contenders opening their campaigns against slightly inferior opponents. When the dust settled, neither team looked particularly sharp as only one goal was&amp;nbsp;...</ht:news_item_snippet>
                <ht:news_item_url>https://www.cbssports.com/soccer/world-cup/news/2019-womens-world-cup-scores-highlights-canada-squeaks-by-japan-underwhelms-argentina-gets-historic-point/</ht:news_item_url>
                <ht:news_item_source>CBSSports.com</ht:news_item_source>
            </ht:news_item>
            <ht:news_item>
                <ht:news_item_title>&lt;b&gt;2019 Women&amp;#39;s World Cup&lt;/b&gt; scores, highlights, recap: Japan underwhelms in opener as Argentina gets historic point</ht:news_item_title>
                <ht:news_item_snippet>Day 4 of the &lt;b&gt;2019 World Cup&lt;/b&gt; has a small slate of action with just two games, but two contenders to win the tournament were scheduled to play their opener. After the first match, we may just be talking about one contender. With talented Canada set to play&amp;nbsp;...</ht:news_item_snippet>
                <ht:news_item_url>https://www.cbssports.com/soccer/world-cup/news/2019-womens-world-cup-scores-highlights-recap-japan-underwhelms-in-opener-as-argentina-gets-historic-point/</ht:news_item_url>
                <ht:news_item_source>CBSSports.com</ht:news_item_source>
            </ht:news_item>
        </item>

So it is simple! We have an Item which has a list of NewsItem. We separately have a Source entity that will be reused between NewsItem. Also, a Country is needed for the Item specify the country/language. That's all.

Creating the graph of entities

The domain model will be a separate package in our application. We choose to create a sub-domain named newsfeed (complete name com.colaru.cindykat.domain.newsfeed) for naming the package because it is possible to have other aggregates (groups of entities) in the future.

It is a good idea to create an entire module for the domain (in a multi-module Maven/Gradle project) because in this way we can create a dependency to it in any other modules of the application - all the other modules will use the domain model.

First, using the IDE, we will create an Item class, which is the primary entity (aggregate root in DDD terminology) and then the rest of the classes:

package com.colaru.cindykat.domain.newsfeed;

import java.util.Date;
import java.util.List;

public class Item {

    private String title;
    private List<Tag> description;
    private String link;
    private String picture;

    private Date pubDate;
    private String pubDateAsString;

    private String approxTraffic;
    private Long approxTrafficAsNumber;

    private List<NewsItem> items;
    private Country country;

    // generate getters/setters using the IDE
}



public class NewsItem {
    private String title;
    private String snippet;
    private String url;
    private Source source;
}


public class NewsItem {
    private String title;
    private String snippet;
    private String url;
    private Source source;
}

public class Country {
    private String name;
    private String countryCode;
    private String languageCode;
    private String flag;
}

public class Source {
    private String name;
    private String url;
}

public class Tag {
    private String name;
}

In the end, we will have this graph of entities:

Entities graph

Now the project filesystem is like this:

Project structure

Anemic Domain

A common problem for a domain is an anti-pattern named Anemic Domain by Martin Fowler - the classes are having just state and not behaviour. We get from the real word only the nouns and not the verbs. In this case, the entities are data structures like in the functional programming language and not real Java objects as Uncle Bob is describing in this article. And this is something normal because in general, the entities are our mappers to database tables used by an ORM persistence framework.

We expect that the verbs will be in the services layer of our application outside the domain. But in this case another discussion is starting: are this services part of the domain?

But also there it is no problem to introduce some logic directly in the entities - in this case, we have to be aware that we will have business logic in two places: services and in the domain model.

We will introduce some business methods in the Item entity (some convertors from String to Date and from String to Long):

public class Item {
        private Date pubDate;
    private String pubDateAsString;
    private String approxTraffic;
    private Long approxTrafficAsNumber;

    // other private fields

    public Date convertStringToDate(String pubDateAsString) throws ParseException {
        SimpleDateFormat parser = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z"); // Wed, 21 Dec 2016 13:00:00 +0200
        return parser.parse(pubDateAsString);
    }

    public Long convertStringToLong(String approxTraffic) {
        return new Long(approxTraffic.toString().replaceAll(",", "").replace("+", ""));
    }
}

Testing the domain

Even if the domain is simple, it is a good idea to start playing with it using and creating some tests using JUnit. First, we have to include the JUnit library as part of our Maven dependencies in pom.xml:

    <!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-api</artifactId>
        <version>5.3.2</version>
        <scope>test</scope>
    </dependency>

Now we can create a first test to play with our simple graph of objects and to verify some small functionalities. This is a first working skeleton good enough to start our TDD development guided by tests for the next features we will add to this app:

class DomainModelTests {

    private Item item;

    @BeforeEach
    void setUp() throws ParseException {
        item = new Item();
        item.setTitle("Women's World Cup 2019");

        item.setLink("https://trends.google.com/trends/trendingsearches/daily?geo=US#Women's%20World%20Cup%202019");
        item.setPicture("https://t2.gstatic.com/images?q=tbn:ANd9GcTW4UzPHNC9qjHRxBr6kCUEns71l8XK6HYcmLpJbhlfZWUbeBQPiia1GDzN3Ehl7nfD-HPbgnG_");

        // tags
        Tag tag = new Tag();
        tag.setName("Word cup");
        List<Tag> tags = new ArrayList<>();
        tags.add(tag);
        item.setDescription(tags);

        NewsItem newsItem = new NewsItem();
        // source
        Source source = new Source();
        source.setName("USA TODAY");
        newsItem.setSource(source);
        newsItem.setTitle("&lt;b&gt;2019 Women&amp;#39;s World Cup&lt;/b&gt; scores, highlights: Canada squeaks by, Japan underwhelms, Argentina gets historic point");
        List<NewsItem> items = new ArrayList<>();
        items.add(newsItem);
        item.setItems(items);
    }

    @Test
    void buildNewItemTest()  {
        Assert.assertEquals(1, item.getItems().size());
        Assert.assertEquals(1, item.getDescription().size());
    }

    @Test
    void convertStringToLongTest() {
        String approxTraffic = "900,000+";
        item.setApproxTraffic(approxTraffic);
        item.setApproxTrafficAsNumber(item.convertStringToLong(approxTraffic));
        Assert.assertEquals(900000, item.getApproxTrafficAsNumber().longValue());
    }

    @Test
    void convertStringToDateTest() {
        String pubDateAsString = "Mon, 1 Jun 2020 09:00:00 -0700";
        item.setPubDateAsString(pubDateAsString);
        try {
            item.setPubDate(item.convertStringToDate(pubDateAsString));
        } catch (ParseException e) {
            e.printStackTrace();
        }
        Calendar cal = new Calendar.Builder().setCalendarType("iso8601")
                .setFields(YEAR, 2020, DAY_OF_MONTH, 1, MONTH, 5, HOUR, 18, MINUTE, 0, SECOND, 0)
                .build();

        Assert.assertEquals(cal.getTime(), item.getPubDate());
    }
}

When we run all the test the bar is green (I write the test first, then the tested method, the test run is failing, I add the implementation, I run the test again, and now it is passing):

Conclusion

In this article, we've exposed how we created a simple model for a small application.

As we can see, the domain model is the core part of the application. This classes that are composing the domain model are technology agnostic (concurrency, persistence is not in our interest now) - are describing just the reality of the business - nothing regarding the technologies used in the project.

Git Repository

We published the sources on GitHub:

git clone https://github.com/colaru/cindykat-springboot.git
cd cindykat-springboot
mvn clean install // the tests will be executed successfully

Inspiration links for this article

Posted on by:

colaru profile

Cristian Olaru

@colaru

Software Craftsman and Full Stack JAVA Developer

Discussion

markdown guide
 

This is a beautiful article, I wish you have gone further to show persistence, the role of value objects and how all this can be exposed via APIs. There are absolutely no articles out there about these topics.

 

I though the same thing. Nice article.

I'd like to see how the project would be organized in terms of controllers, services, repository and entities.