What You'll Learn

So far, we've explored the mechanics to Spring MVC to handle simple requests. Now, we'll focus on the domain of our application - namely charity donations.

We'll introduce a class to represent charities and other classes that will allow us to manage access to the set of charities and to take actions on charities.

Create a sub-package called "domain" and in that package, create a class called "Charity". We will use Lombok to provide standard methods.

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Charity {
    private String name;
    private String registration;
    private String acronym;
    private String logoFileName;
    private String missionStatement;
    private Boolean isActive;
}

The three class annotations are all provided by Lombok. The @Data annotation tells Lombok to generate getters and setters for all non-final fields as well as other standard methods such as equals(), hashcode() and toString(). The other two annotations should be self-explanatory in terms of the constructors that they create.

The Charity class represents a single chartity, but we know that there are many (indeed 1000s) of charities so we need a class that can provide us with access to subsets of charities. In Spring Boot (indeed, in wider design vocabulary), this type of class is known as a repository. Typically, repositories provide access to databases, but to start with, we will create a hard-coded version.

Add a mock repository

Create a sub-package called "data". This should be level in the hierarchy with the "domain" package . If you are unsure, check the Gitlab repository.

In that new package, create a class called "MockCharityRepository" and enter this code (adding your own data for the missing charity contructors).

public class MockCharityRepository {
    private static MockCharityRepository singleton = new MockCharityRepository();
    private List<Charity> charities;

    private MockCharityRepository() {

        charities = new ArrayList<>();

        Charity nspcc =
                new Charity("National Society for the Prevention of Cruelty to Children"
                        , "11111111"
                        , "NSPCC"
                        , "nspcc"
                        , "Making Children Safe"
                        , Boolean.TRUE);
        Charity rspca = ...
        Charity oxfam = ...
        Charity bhf = ...

        List<Charity> charityList = List.of(rspca, nspcc, oxfam, bhf);
        charities.addAll(charityList);
    }

    public static MockCharityRepository getInstance() {
        return singleton;
    }

    public List<Charity> findAll() {
        return charities;
    }
}

This class implements the singleton pattern. Patterns are discussed elsewhere in the module. A singleton is a class that only has one instance (per JVM). This is implemented by having a static method called getInstance() and a private constructor. A single instance of the class is created when the class is initialised and that single instance is returned to a client object when getInstance() is called.

Once the client has that single instance, it acts like any other object and the client can call the public methods. In this case, there is one other public method that returns the list of charities that are created at initialisation time.

By doing this, we have provided a mock implementation of a repository and we have a source of Charity objects.

At this point, we're going to make an architectural decision.

We're going to introduce a service layer. The methods on the service layer represent the actions that can be taken upon the domain. At this point, these actions are limited to searches, but we'll add more later. What's important here is that the actions are not bound into any particular user interface. They are unaware of how the data is presented or how the action was initiated.

Provide an interface to the service layer

It is also a good principle to code to interfaces rather than implementations. By doing this, we are separating ourselves from a specific implementation and only depending on an expression of ‘what' is provided, rather than ‘how' it is provided. This provides for changes to the implementation to be introduced later.

Create a new package

Create a sub-package (level with "data" and "domain") called "service". Within this service package, creates two sub-packages called "dto" and "impl". The purpose of these will become apparent later.

Create an interface for the service

In the new "service" package, create a Java interface with this code:

public interface CharitySearch {
    List<CharityDTO> findAll();
}

At this point, we have an interface with a single method on it, that will return all charities. However, notice that the return type is List not List. What is this DTO class and why are we using it?

Let's look at the DTO class first and then answer those questions.

The Charity DTO classes

In the "dto" package, create a class called "CharityDTO" with this code:

@Value
@AllArgsConstructor
public class CharityDTO {
    String name;
    String registration;
    String acronym;
    String logoFileName;
    String missionStatement;
    Boolean isActive;

    public CharityDTO(Charity aCharity) {
        this(
                aCharity.getName(),
                aCharity.getRegistration(),
                aCharity.getAcronym(),
                aCharity.getLogoFileName(),
                aCharity.getMissionStatement(),
                aCharity.getIsActive());
    }
}

This class has the Lombok annotation @Value rather than @Data. This annotation is an implementation of the value object pattern. Value objects are purely representations of some data and are immutable - that is, they can't be changed. In Lombok terms, this means that it creates getters but not setters.

Why use DTOs?

By using DTOs, we are preventing the calling class from changing the charity domain object and in so doing, we are forcing the client class to only use the service interface to do work on charity domain objects. This puts the responsibility for the coordination of actions firmly into the service layer.

As we will see later when we discuss APIs, this clear break is a common occurance in modern web applications, where the domain data is distributed over the web using a textual representation (typically JSON or XML). When the client system receives that data, it can create it's own object, but that new object is completely separated from the domain, so no accidental, uncontrolled actions can be taken on that domain object directly.

It is worth noting that the DTO class contains a constructor that takes a domain object as a parameter.

Add the implementation of the service

In the new "impl" package, create a class called "CharitySearchImpl" and enter this code:

public class CharitySearchImpl implements CharitySearch {

    private MockCharityRepository mockCharityRepository = MockCharityRepository.getInstance();

    @Override
    public List<CharityDTO> findAll() {
        return mockCharityRepository
                .findAll()
                .stream()
                .map(c -> new CharityDTO(c))
                .collect(Collectors.toList());
    }
}

This service implementation uses the previously created repository. Notice how the getInstance() method is used to instantiate the field. Also notice that the implementation "implements" the interface. This will become more important later.

The findAll() method then uses streams to get all of the charity domain objects from the repository, map them from domain objects to DTOs (using the aforementioned constructor) and then collect the mapped objects back into a List.

We would like to test this code, but it is not connected to the controller. We can test the code using an automated test.

In the test source tree, create a "service" package (again, check the repository if you're not sure) and then create a class called CharitySearches with this code:

public class CharitySearches {
    @Test
    public void shouldGet4Charities() throws Exception {
        CharitySearch charityService = new CharitySearchImpl();
        List<CharityDTO> charityDTOList = charityService.findAll();
        assertEquals(4, charityDTOList.size());
    }
}

This is a standard JUnit test. In the test, a CharitySearch object (note we use the interface) is instantiated. The findAll() method is called to return a list of DTOs, and we assert that the list size is 4. We could add a number of other tests as well to check for the order of the DTOs and/or for the contents of their fields.

The test can be run by using gradle > build > build or by using the context menu in IntelliJ (right click on "test" or a sub-package, and then run the tests).

This has been quite a big step because we've made a clear architectural statement about how our application's user interface will have to interact with the core of the application (where the business rules of the application will reside).