Статьи

Spring — Разработка модели предметной области и сервисного уровня

Мы собираемся создать приложение для управления расписаниями. Итак, давайте сначала подумаем о некоторых случаях использования и сущностях. Позвольте мне написать их в нескольких пулях:

  1. Задача назначается сотруднику менеджером. Одна задача может быть назначена многим сотрудникам.
  2. Сотрудник заполняет количество часов, которое он отработал над определенным заданием в системе.
  3. Менеджер / Сотрудник просматривает отчеты в расписаниях (расписания могут быть изменены).

Давайте немного вернемся к этим пунктам и попробуем преобразовать этот простой человеческий язык в некоторые отношения и сущности, которые может обнаружить программист.

  • Объекты: менеджер, сотрудник, расписание, задача

Хорошо, теперь мы должны лучше понять домен, поэтому давайте создадим проект maven и реализуем классы. С Maven вы получаете красивую и чистую структуру проекта. Все, что вам нужно, это установить Maven и иметь pom.xml в вашем проекте. Вы можете сделать это «вручную» и создать приложение через терминал (в этом случае просто создайте обычный проект и добавьте файл pom.xml). Я предпочитаю использовать некоторые дополнительные инструменты. IntelliJ IDEA, NetBeans и Springsource Tool Suite имеют встроенную поддержку Maven. Если вы используете простой Eclipse, проверьте плагин m2eclipse .

В любом случае, вот некоторая базовая конфигурация Maven для нашего проекта:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
   
 <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>

Теперь давайте реализуем модель предметной области. Создайте пакет org.timesheet.domain и определите следующие классы.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
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;
 }
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
package org.timesheet.domain;
 
public class Manager {
 
 private String name;
 
 public Manager(String name) {
  this.name = name;
 }
  
 public String getName() {
  return name;
 }
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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;
 }
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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
    + ']';
 }
 
}

Как вы можете видеть, классы Manager и Employee не имеют много свойств, они здесь просто для того, чтобы иметь безопасную модель типа. В «реальном мире» у них, вероятно, были бы другие свойства, такие как фамилия, день рождения, адрес и т. Д., Возможно, даже общий родительский класс.
Кроме того, нас сейчас не волнуют различные ограничения. Например, мы можем заполнять только целые часы на задачах и так далее.

Теперь пришло время определить наш уровень обслуживания — определить бизнес-операции и установить интерфейс для них. Итак, давайте сделаем пакет org.timesheet.service . Сначала мы создадим интерфейс GenericDao, где мы определим основные операции CRUD для каждого объекта в системе.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
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();
  
}

А пока давайте не будем беспокоиться о реальном уровне персистентности — давайте создадим фиктивную реализацию и сохраним все данные в памяти. Мы поместим его в новый пакет — org.timesheet.service.impl . Не волнуйтесь, позже мы будем использовать Hibernate для этого. Вот код для фиктивной реализации:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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;
 }
 
}

Далее мы напишем наш первый простой тест. Теперь мы добавим нашу первую зависимость в файл pom.xml в библиотеку JUnit. Поскольку он первый, нам также нужно заключить его в элемент зависимостей следующим образом:

1
2
3
4
5
6
7
<dependencies>
 <dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.10</version>
 </dependency>
</dependencies>

Вот наш первый очень простой модульный тест для сотрудника DAO. Мы не будем делать другие сейчас, потому что нам пока нечего тестировать. Более важно то, насколько мы зависим от реализации DAO в тесте (мы используем новый InMemoryDao… ). Это плохо, потому что мы должны тестировать только открытый API определенного интерфейса. Позже в этом уроке вы увидите, как Spring помогает нам решать такие проблемы.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
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());
 }
 
}

При желании вы также можете написать модульные тесты для оставшихся тестов для других DAO. Но так как у нас нет правильной реализации для тестирования сейчас, мы сделаем это позже вместе.

Однако не всегда все так просто. Речь идет не только о CRUD-операциях, но и о бизнес- операциях, которые не являются достаточно общими для выражения в простых DAO. Итак, давайте определим несколько бизнес-операций и создадим для них отдельный сервис. Мы назовем этот сервис TimesheetService.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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);
  
}

Хорошо, пока все хорошо. Теперь у вас есть представление о том, какой бизнес-домен мы будем использовать в следующих примерах. Вы можете быть удивлены сейчас — мы еще не использовали Spring, почему? Помните, что первоначальная цель Spring состоит в том, чтобы упростить разработку Java на предприятии и поддержать модель разработки POJO. Поэтому будет очень легко использовать Spring с этой базовой моделью, поэтому мы не будем смешивать основную логику с ненужными зависимостями.

На картинке ниже представлена ​​структура проекта, который мы уже создали, поэтому убедитесь, что вы хороши.

Ссылка: Часть 1. Разработка модели предметной области и уровня обслуживания от нашего партнера JCG Михала Вртиака в блоге vrtoonjava .