Spring – Designing the domain model and the service layer
- Task is assigned to employee by manager. One task can be assigned to many employees.
- Employee fills the amount of hours that he worked on certain task to the system.
- Manager/Employee views reports on timesheets (timesheets can be altered).
Let’s revisit those points a little and let’s try to transform this plain human language to some relations and entities that programmer can spot.
- Entities: Manager, Employee, Timesheet, Task
Okay, we should now have better grasp about the domain, so let’s create maven project and implement classes. With Maven you get nice and clean project structure. All you need is installed Maven and having pom.xml in your project. You can either do that “by hand” and building application via terminal (in this case just create regular project and add pom.xml file). I prefer using some additional tooling. IntelliJ IDEA, NetBeans and Springsource Tool Suite have out of the box Maven support. If you’re using plain Eclipse, check m2eclipse plugin.
In either way, here is some basic Maven configuration for our project:
<project xmlns='http://maven.apache.org/POM/4.0.0' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xsi:schemaLocation='http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd'> <modelVersion>4.0.0</modelVersion> <groupId>org.timesheet</groupId> <artifactId>org.timesheet</artifactId> <version>0.0.1-SNAPSHOT</version> <name>Timesheet Management On Spring</name> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.6</source> <target>1.6</target> </configuration> </plugin> </plugins> </build> </project>
Now let’s implement domain model. Create package org.timesheet.domain and define following classes.
package org.timesheet.domain; public class Employee { private String name; private String department; public Employee(String name, String department) { this.name = name; this.department = department; } public String getName() { return name; } public String getDepartment() { return department; } }
package org.timesheet.domain; public class Manager { private String name; public Manager(String name) { this.name = name; } public String getName() { return name; } }
package org.timesheet.domain; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class Task { private List<Employee> assignedEmployees = new ArrayList<Employee>(); private Manager manager; private boolean completed; private String description; public Task(String description, Manager manager, Employee... employees) { this.description = description; this.manager = manager; assignedEmployees.addAll(Arrays.asList(employees)); completed = false; } public Manager getManager() { return manager; } public List<Employee> getAssignedEmployees() { return assignedEmployees; } public void addEmployee(Employee e) { assignedEmployees.add(e); } public void removeEmployee(Employee e) { assignedEmployees.remove(e); } public void completeTask() { completed = true; } }
package org.timesheet.domain; public class Timesheet { private Employee who; private Task task; private Integer hours; public Timesheet(Employee who, Task task, Integer hours) { this.who = who; this.task = task; this.hours = hours; } public Employee getWho() { return who; } public Task getTask() { return task; } public Integer getHours() { return hours; } /** * Manager can alter hours before closing task * @param hours New amount of hours */ public void alterHours(Integer hours) { this.hours = hours; } @Override public String toString() { return 'Timesheet [who=' + who + ', task=' + task + ', hours=' + hours + ']'; } }
As you can see, Manager and Employee classes don’t have many properties, they’re here just for the sake of having type safe model. In the “real world”, they’d probably have various other properties like surname, birthday, address and so on, maybe even common parent class.
Also, we don’t really care about various constraints now. For example, we can only fill integer hours on tasks and so on.
Now it’s time to define our service layer – define business operations and establish interface for those. So let’s make package org.timesheet.service. At first, we will create GenericDao interface, where we will define basic CRUD operations for every entity in the system.
package org.timesheet.service; import java.util.List; public interface GenericDao<E, K> { void add(E entity); void update(E entity); void remove(E entity); E find(K key); List<E> list(); }
For now, let’s not worry about the actual persistence layer – let’s create some dummy implementation and store all the data in memory. We will put it in to the new package – org.timesheet.service.impl. Don’t worry, later we’ll use Hibernate for this. Here is the code for the dummy implementation:
package org.timesheet.service.impl; import java.util.ArrayList; import java.util.List; import org.timesheet.service.GenericDao; public class InMemoryDao<E, K> implements GenericDao<E, K> { private List<E> entities = new ArrayList<E>(); @Override public void add(E entity) { entities.add(entity); } @Override public void update(E entity) { throw new UnsupportedOperationException('Not supported in dummy in-memory impl!'); } @Override public void remove(E entity) { entities.remove(entity); } @Override public E find(K key) { if (entities.isEmpty()) { return null; } // just return the first one sice we are not using any keys ATM return entities.get(0); } @Override public List<E> list() { return entities; } }
Next, we will write our first simple test. We will now add our first dependency into pom.xml file to JUnit library. Because it’s the first one, we also need to wrap it into dependencies element like so:
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.10</version> </dependency> </dependencies>
Here’s our first very simple unit test for Employee DAO. We won’t do others now, since we don’t really have anything to test yet. What’s more important though, is how we depend on implementation of DAO in the test (we use new InMemoryDao…). This is bad, because we should only test public API of defined interface. Later in this tutorial, you will see how Spring helps us to solve such problems.
package org.timesheet.service; import static org.junit.Assert.*; import java.util.List; import org.junit.Before; import org.junit.Test; import org.timesheet.domain.Employee; import org.timesheet.service.impl.InMemoryDao; public class EmployeeDaoTest { private GenericDao<Employee, Long> employeeDao = new InMemoryDao<Employee, Long>(); @Before public void setUp() { for (int i = 0; i < 5; i++) { Employee e = new Employee('Mike ' + i, 'IT'); employeeDao.add(e); } } @Test public void testAdd() { int oldSize = employeeDao.list().size(); Employee e = new Employee('Bob', 'IT'); employeeDao.add(e); int newSize = employeeDao.list().size(); assertFalse (oldSize == newSize); } @Test public void testRemove() { int oldSize = employeeDao.list().size(); Employee e = employeeDao.find(1L); employeeDao.remove(e); int newSize = employeeDao.list().size(); assertFalse (oldSize == newSize); } @Test public void testUpdate() { //TODO: need real implementation } @Test public void testList() { List<Employee> list = employeeDao.list(); assertNotNull (list); assertFalse (list.isEmpty()); } }
If you want, you can also write unit tests for remaining tests for the other DAOs. But since we don’t have proper implementation to test now, we’ll do it later together.
Things are not always so easy though. It’s not only about CRUD operations, it’s also about business operations that are not generic enough to be expressed in simple DAOs. So let’s define few business operations and create separate service for them. We’ll call this service TimesheetService.
package org.timesheet.service; import org.timesheet.domain.Employee; import org.timesheet.domain.Manager; import org.timesheet.domain.Task; import java.util.List; /** * Business that defines operations on timesheets */ public interface TimesheetService { /** * @return Finds the busiest task (with the most of employees). * Returns {@code null} when tasks are empty. */ Task busiestTask(); /** * Finds all the tasks for the employee. * @param e Employee * @return Tasks */ List<Task> tasksForEmployee(Employee e); /** * Finds all the tasks for the manager. * @param m Manager * @return Tasks */ List<Task> tasksForManager(Manager m); }
Okay, so far so good. You have now idea what’s the business domain we will be using in the next examples. You might be wondering now – we haven’t use any Spring yet, why? Remember that Spring’s original purpose is to simplify enterprise java development and encourage POJO development model. So it will be very easy to use Spring with this basic model, so we won’t have our core logic mixed with unnecessary dependencies.
On the picture below there is structure of the project we’ve built so far, so make sure you’re good.
Reference: Part 1 – Designing the domain model and the service layer from our JCG partner Michal Vrtiak at the vrtoonjava blog.
It looks like anemic domain model: http://www.martinfowler.com/bliki/AnemicDomainModel.html
I don’t quite understand what is it that you are testing in EmployeeDaoTest. Aren’t you testing the DAO implementation? If so, what’s wrong with having a dependency to it?