Проектирование	
  REST	
  
приложения	
  	
  
или,	
  нужно	
  ли	
  программисту	
  думать	
  ?	
  
Задача	
  
Ведение	
  списка	
  пользователей	
  (почти	
  CRUD)	
  
•  Создаем	
  пользователя	
  по	
  имени	
  (с	
  присвоением	
  
идентификатора)	
  (С)	
  
•  Просматриваем	
  список	
  пользователей	
  (R)	
  
•  Получаем	
  пользователя	
  по	
  идентификатору	
  (R)	
  
	
  
Monolithic	
  applicaRon	
  
(Монолит)	
  
@Timed
@POST @Path("/add")
public Map<String, Object> add(String name) {
try (Handle h = dbi.open()) {
int id = h.createStatement("insert into user (name) values (:name)").bind("name", name)
.executeAndReturnGeneratedKeys(IntegerMapper.FIRST).first();
return find(id);
}
}
@Timed
@GET @Path("/{id}")
public Map<String, Object> find(@PathParam("id") Integer id) {
try (Handle h = dbi.open()) {
return h.createQuery("select id, name from user where id = :id").bind("id", id).first();
}
}
@Timed
@GET @Path("/all")
public List<Map<String, Object>> all(@PathParam("id") Integer id) {
try (Handle h = dbi.open()) {
return h.createQuery("select * from user").list();
}
}
Монолит	
  –	
  Добро	
  или	
  зло	
  ?	
  
•  Нарушен	
  принцип	
  SRP	
  
принцип	
  единственной	
  обязанности	
  	
  
(single	
  responsibility	
  principle)	
  
•  класс	
  или	
  модуль	
  должны	
  иметь	
  одну	
  и	
  
только	
  одну	
  причину	
  измениться	
  
Я	
  хочу	
  иметь	
  возможность	
  сосредоточиться	
  
на	
  сложных	
  аспектах	
  системы	
  по	
  
отдельности,	
  поэтому	
  когда	
  мне	
  
становится	
  сложно	
  это	
  делать,	
  я	
  начинаю	
  
разбивать	
  классы	
  и	
  выделять	
  новые	
  
	
  
[Дядюшка	
  Боб]	
  
Separated	
  PresentaRon	
  
	
  
•  Отделенное	
  представление	
  
Смысл	
  отделенного	
  представления	
  в	
  том,	
  чтобы	
  провести	
  четкую	
  границу	
  между	
  
доменными	
  объектами,	
  которые	
  отражают	
  наш	
  реальный	
  мир,	
  и	
  объектами	
  
представления	
  
	
  
[MarRn	
  Fowler]	
  
Разделяем	
  приложение	
  
View	
  
@POST @Path("/add")
public User add(String name) {
return find(dao.insert(name));
}
@GET @Path("/{id}")
public User find(@PathParam("id") Integer id) {
return dao.findById(id);
}
@GET @Path("/all")
public List<User> all(@PathParam("id") Integer id) {
return dao.all();
}
Domain	
  
public class User {
@JsonProperty public final int id;
@JsonProperty public final String name;
public User(int id, String name) {
this.id = id;
this.name = name;
}
}
Data	
  Access	
  (JDBI)	
  
@RegisterMapper(UserMapper.class)
public interface UserDAO {
@SqlUpdate("insert into user (name) values (:name)")
@GetGeneratedKeys
int insert(@Bind("name") String name);
@SqlQuery("select * from user where id = :id")
User findById(@Bind("id") int id);
@SqlQuery("select * from user")
List<User> all();
}
Усложняем	
  бизнес	
  логику	
  
•  Пользователь	
  всегда	
  принадлежит	
  определенной	
  группе	
  	
  
(нужно	
  указывать	
  группу	
  при	
  создании)	
  
	
  
•  В	
  группу	
  может	
  входить	
  не	
  более	
  5	
  
пользователей	
  	
  
(необходима	
  проверка)	
  
	
  
•  Куда	
  разместим	
  эту	
  логику	
  ?	
  
Очевидное	
  решение	
  	
  
Вся	
  логика	
  на	
  контроллере	
  
@POST @Path("/add")
public Response add(User user) {
Integer count = userDAO.findCountUsersByGroup(user.groupId);
if (count == 5) {
return status(BAD_REQUEST)
.entity("More users than allowed in group")
.type("text/plain")
.build();
}
return ok(find(userDAO.insert(user.name, user.groupId)))
.build();
}
И	
  чтение	
  
@GET @Path("/{userId}")
public User find(@PathParam("userId") Integer userId) {
User user = userDAO.findById(userId);
user.setGroup(groupDAO.findGroupById(user.groupId));
return user;
}
@Timed
@GET @Path("/all")
public List<User> all() {
List<User> users = userDAO.all();
users.stream().forEach
(u -> u.setGroup(groupDAO.findGroupById(u.groupId)));
return users;
}
FSUC	
  
Fat	
  Stupid	
  Ugly	
  Controllers	
  
[Pádraic	
  Brady	
  (Zend	
  Framework)]	
  
	
  
ТТУК	
  -­‐	
  Толстые	
  тупые	
  уродливые	
  контроллеры	
  	
  
Среднестатистический	
  ТТУК	
  получал	
  данные	
  из	
  БД	
  (используя	
  уровень	
  абстракции	
  
базы	
  данных,	
  делая	
  вид,	
  что	
  это	
  модель)	
  или	
  манипулировал,	
  валидировал,	
  
записывал,	
  а	
  также	
  передавал	
  данные	
  в	
  Вид	
  
	
  
Такой	
  подход	
  очень	
  популярен	
  
	
  Контроллер	
  -­‐	
  Модель	
  мутант	
  
FSUC	
  –	
  Добро	
  или	
  зло	
  ?	
  
•  Толстый	
  (содержащий	
  модель)	
  контроллер	
  практически	
  не	
  поддается	
  переносу	
  и	
  
тестированию	
  (TDD)	
  
–  мы	
  не	
  можем	
  создать	
  экземпляр	
  класса	
  контроллера	
  вне	
  фреймворка,	
  контроллер	
  жестко	
  
привязан	
  к	
  определенному	
  фреймворку	
  
–  Вид	
  должен	
  по	
  возможности	
  избегать	
  контакта	
  с	
  Контроллером	
  и	
  получать	
  данные	
  
напрямую	
  из	
  модели	
  
•  Но,	
  мне	
  не	
  надо	
  думать	
  куда	
  разместить	
  бизнес	
  логику	
  
•  И	
  один	
  класс	
  на	
  все	
  случаи	
  жизни.	
  Что	
  может	
  быть	
  проще	
  !	
  
	
  
Альтернативы	
  
•  Переносим	
  логику	
  в	
  выделенный	
  сервис	
  
–  Процедурный	
  стиль	
  программирования	
  
•  Переносим	
  логику	
  в	
  доменный	
  объект	
  
–  Объектно-­‐ориентированный	
  стиль	
  	
  
Процедурный	
  стиль	
  
@POST @Path("/add")
@Consumes(MediaType.APPLICATION_JSON)
public Response add(User user) {
try {
return ok(service.insert(user)).build();
} catch (RuntimeException e) {
return status(BAD_REQUEST).entity(e.getMessage())
.type("text/plain")
.build();
}
}
public User insert(final User user) {
Integer count = userDAO.findCountUsersByGroup(user.groupId);
if (countUsersByGroup == 5) {
throw new IllegalStateException("More users than allowed");
}
return (find(userDAO.insert(user.name, user.groupId)));
}
Как	
  
Назвать	
  
Сервис	
  
?	
  
ООП	
  
•  В	
  каком	
  	
  сервисе	
  искать	
  необходимый	
  нам	
  метод	
  ?	
  
	
  	
  
•  Очень	
  часто	
  на	
  руках	
  у	
  нас	
  имеется	
  только	
  доменный	
  объект	
  
•  Не	
  проще	
  ли	
  вызвать	
  метод	
  имеющегося	
  на	
  руках	
  доменного	
  объекта	
  ?	
  
•  Ясный	
  код	
  при	
  котором	
  не	
  нужно	
  помнить,	
  что	
  куда	
  положил	
  
public User add(User user) {
return user.insert();
}
public User add(User user) {
return service.insert(user);
}
Однако	
  
ООП	
  порождает	
  много	
  больше	
  вопросов,	
  чем	
  старый	
  добрый	
  
процедурный	
  подход	
  
1.  Как	
  из	
  доменных	
  объектов	
  дотянутся	
  до	
  DAO	
  
2.  Как	
  расправиться	
  с	
  циклическими	
  зависимостями	
  	
  
DAO	
  объекты	
  ß	
  à	
  Доменные	
  объекты	
  
3.  Куда	
  разместить	
  методы	
  не	
  относящиеся	
  к	
  доменным	
  
объектам	
  
Снова	
  службы	
  ?	
  
Как	
  из	
  доменных	
  объектов	
  дотянутся	
  до	
  
DAO	
  
1.  Радикальное	
  решение	
  –	
  втянуть	
  DAO	
  объекты	
  в	
  доменные	
  	
  
(AcRve	
  Record)	
  
Замечательная	
  сцепленность	
  
Нет	
  класса	
  –	
  нет	
  проблемы	
  	
  
А	
  как	
  насчет	
  SRP	
  ?	
  
2.  Singleton	
  
Антипатерн	
  
3.  Service	
  Locator	
  
Антипатерн	
  
4.  Обращение	
  зависимости	
  (DIP)	
  
1.  Передача	
  через	
  параметры	
  
2.  Внедрение	
  зависимостей	
  (Dependency	
  InjecRon)	
  
5.  Комбинации	
  вышеприведенных	
  подходов	
  	
  )	
  
Богатый	
  (Rich)	
  доменный	
  	
  объект	
  и	
  
Service	
  Locator	
  
public User insert() {
UserDAO userDAO = service(UserDAO.class);
Integer count = userDAO.findCountUsersByGroup(groupId);
if (count == 5) {
throw new IllegalStateException("More users than allowed");
}
this.id = userDAO.insert(name, groupId);
return this;
}
Все	
  те	
  же	
  и	
  ленивая	
  (отложенная)	
  
инициализация	
  
Lazy Initialization
@JsonProperty
public String getGroupName() {
if (groupName == null) {
GroupDAO groupDAO = service(GroupDAO.class);
groupName = groupDAO.findGroupById(groupId).name;
}
return groupName;
}
Бизнес	
  сервисы	
  	
  ООП	
  
•  Бывают	
  случаи	
  когда	
  метод	
  не	
  привязывается	
  к	
  доменному	
  
объекту	
  	
  
– В	
  качестве	
  параметра	
  не	
  передается	
  доменный	
  объект	
  
– В	
  качестве	
  параметра	
  передается	
  несколько	
  доменных	
  
объектов,	
  и	
  сложно	
  предпочесть	
  один	
  из	
  них	
  
– Логика	
  метода	
  сложна	
  и	
  (или)	
  опциональна	
  и	
  привязка	
  к	
  
доменному	
  объекту	
  нарушает	
  сразу	
  множество	
  принципов	
  )	
  
•  В	
  этих	
  случаях	
  формируются	
  бизнес	
  сервисы	
  (уровня	
  домена),	
  
и	
  методы	
  переносятся	
  в	
  них	
  	
  
Бизнес	
  сервисы	
  DDD	
  
•  Некоторые	
  бизнес	
  сервисы	
  типичны	
  и	
  с	
  целью	
  
повышения	
  ясности	
  системы	
  могут	
  быть	
  
классифицированы	
  
•  Примеры	
  из	
  практики	
  «Проектирование	
  ведомое	
  бизнес	
  
логикой»	
  DDD	
  
– Фабрика	
  –	
  сервис	
  создающий	
  новый	
  экземпляр	
  класса	
  
– Репозиторий	
  –	
  сервис	
  предоставляющий	
  доступ	
  к	
  
коллекции	
  экземпляров	
  определенного	
  класса	
  
	
  
	
  
	
  
[Eric	
  Evans]	
  
Пример	
  репозитория	
  
public class UserRepository implements Repository<User> {
private final UserDAO userDAO;
public UserRepository() {
this.userDAO = service(UserDAO.class);
}
@Override
public User find(final Integer userId) {
return userDAO.findById(userId);
}
@Override
public List<User> findAll() {
return userDAO.all();
}
}
Упорядочиваем	
  связи	
  
1.  Обращаем	
  зависимости	
  
2.  Вводим	
  интерфейсы	
  
3.  Вводим	
  DTO	
  
Альтернативные	
  схемы	
  
Результат	
  (луковичная	
  архитектура)	
  
Объект	
  передачи	
  данных	
  
•  Теперь	
  наши	
  доменные	
  объекты	
  выступают	
  в	
  двух	
  ипостасях	
  
–  Как	
  богатые	
  доменные	
  объекты	
  содержащие	
  бизнес	
  логику	
  
–  Как	
  объекты	
  передачи	
  данных	
  
•  Смешение	
  ответственности	
  приводят	
  к	
  проблемам	
  
–  Валидность	
  доменной	
  модели	
  (все	
  ли	
  поля	
  заполнены	
  при	
  передаче)	
  
–  Противоречивые	
  требования	
  приводят	
  к	
  компромиссам	
  
–  Объект	
  передачи	
  данных,	
  чаще	
  всего,	
  	
  должен	
  быть	
  оптимизирован	
  
под	
  потребности	
  производительности	
  
–  Доменный	
  объект,	
  оптимизируется	
  под	
  повторное	
  использование,	
  
модифицируемость	
  и	
  ясность	
  
•  Из	
  хорошего	
  –	
  не	
  надо	
  перекладывать	
  данные	
  в	
  домен	
  
Передаем	
  данные	
  в	
  систему	
  
(альтернативы)	
  
•  Передаем	
  данные	
  по	
  элементам	
  (декапсуляция)	
  
–  Плюс,	
  высокая	
  производительность	
  
–  Еще	
  один	
  плюс,	
  	
  низкая	
  связанность	
  
–  Минус,	
  при	
  передаче	
  сложных	
  структур	
  полностью	
  теряется	
  ясность	
  
–  Минус,	
  сложнее	
  согласовывать	
  протокол	
  передачи	
  и	
  проводить	
  валидацию	
  данных	
  
	
  
•  Создаем	
  объект	
  передачи	
  данных	
  (DTO)	
  
–  Оптимизирован	
  под	
  производительность	
  (все	
  за	
  один	
  раз)	
  
–  Оптимизирован	
  под	
  целостность	
  (строго	
  типизированные	
  данные)	
  
–  Оптимизирован	
  под	
  модифицируемость	
  (версионность,	
  	
  обратная	
  совместимость)	
  
	
  
	
  
public User add(User user) {
Вводим	
  DTO	
   public class UserDTO {
@JsonProperty public int id;
@JsonProperty public String name;
@JsonProperty public int groupId;
@JsonProperty public String groupName;
public UserDTO() {
}
public User assemble() {
return User.make(id, name, groupId);
}
public static UserDTO from(final User user) {
UserDTO dto = new UserDTO();
dto.id = user.getId();
dto.name = user.getName();
dto.groupId = user.getGroup().getGroupId();
dto.groupName = user.getGroup().getName();
return dto;
}
}
Бонусы	
  
	
  
•  Можем	
  передавать	
  только	
  
необходимую	
  информацию	
  
(возможно,	
  собрав	
  ее	
  из	
  
разных	
  источников)	
  
	
  
•  Можем	
  принимать	
  только	
  то,	
  
что	
  есть	
  в	
  удаленной	
  системе	
  
(одним	
  пакетом,	
  без	
  лишних,	
  
или	
  пустых	
  полей)	
  
	
  
Усложняем	
  бизнес	
  логику	
  
•  Добавим	
  функцию	
  перехода	
  пользователя	
  из	
  группы	
  в	
  группу	
  
•  Что	
  передаем	
  на	
  наш	
  контроллер	
  ?	
  
	
  
•  Очевидное	
  решение	
  UserDTO	
  и	
  GroupDTO	
  
– Необходимо	
  протягивать	
  эти	
  данные	
  через	
  клиента	
  
– Усложняется	
  клиент	
  
– Усложняется	
  интерфейс	
  взаимодействия	
  
Как	
  получить	
  доменный	
  объект	
  	
  
•  На	
  руках	
  только	
  идентификаторы	
  !	
  
•  Очевидное	
  решение	
  –	
  Репозиторий	
  
– Множество	
  избыточных	
  запросов	
  к	
  базе.	
  Медленно	
  
	
  
	
   final User user = repository.find(userId);
user.moveTo(groupId);
Альтернативные	
  варианты	
  получения	
  
доменного	
  объекта	
  	
  
•  Протянуть	
  его	
  через	
  DTO	
  
– Сложно	
  
•  Держим	
  в	
  памяти	
  (например	
  кешируем	
  в	
  репозитории)	
  
– Проблема	
  с	
  распределенными	
  системами	
  
final User user = repository.find(userId);
user.moveTo(groupId);
final User user = dto.assemble()
user.moveTo(groupId);
Оптимизация	
  	
  
(Уменьшение	
  количества	
  запросов)	
  
•  На	
  чтение	
  
– Кэширование	
  
– Связывание	
  (вытаскиваем	
  как	
  можно	
  больше	
  данных	
  одним	
  
запросом)	
  
– JOIN	
  
– UNION	
  
	
  
	
  
•  На	
  запись	
  
– Пакетирование	
  
– Единица	
  работы	
  (Unit	
  of	
  Work)	
  
И	
  напоследок	
  
напутствие	
  от	
  Pádraic:	
  
	
  
Думайте	
  товарищи,	
  желание	
  думать	
  должно	
  быть	
  
основным,	
  спрашивайте	
  везде,	
  анализируйте,	
  тестуйте,	
  
когда	
  что-­‐то	
  вам	
  кажется	
  не	
  разумным.	
  	
  
Если	
  вы	
  все	
  принимаете	
  на	
  чистую	
  веру,	
  значит	
  вы	
  
заслуживаете	
  того,	
  что	
  получаете	
  
	
  

2015-12-06 Максим Юнусов - Проектирование REST приложения, или нужно ли программисту думать

  • 1.
    Проектирование  REST   приложения     или,  нужно  ли  программисту  думать  ?  
  • 2.
    Задача   Ведение  списка  пользователей  (почти  CRUD)   •  Создаем  пользователя  по  имени  (с  присвоением   идентификатора)  (С)   •  Просматриваем  список  пользователей  (R)   •  Получаем  пользователя  по  идентификатору  (R)    
  • 3.
    Monolithic  applicaRon   (Монолит)   @Timed @POST @Path("/add") public Map<String, Object> add(String name) { try (Handle h = dbi.open()) { int id = h.createStatement("insert into user (name) values (:name)").bind("name", name) .executeAndReturnGeneratedKeys(IntegerMapper.FIRST).first(); return find(id); } } @Timed @GET @Path("/{id}") public Map<String, Object> find(@PathParam("id") Integer id) { try (Handle h = dbi.open()) { return h.createQuery("select id, name from user where id = :id").bind("id", id).first(); } } @Timed @GET @Path("/all") public List<Map<String, Object>> all(@PathParam("id") Integer id) { try (Handle h = dbi.open()) { return h.createQuery("select * from user").list(); } }
  • 4.
    Монолит  –  Добро  или  зло  ?   •  Нарушен  принцип  SRP   принцип  единственной  обязанности     (single  responsibility  principle)   •  класс  или  модуль  должны  иметь  одну  и   только  одну  причину  измениться   Я  хочу  иметь  возможность  сосредоточиться   на  сложных  аспектах  системы  по   отдельности,  поэтому  когда  мне   становится  сложно  это  делать,  я  начинаю   разбивать  классы  и  выделять  новые     [Дядюшка  Боб]  
  • 5.
    Separated  PresentaRon     •  Отделенное  представление   Смысл  отделенного  представления  в  том,  чтобы  провести  четкую  границу  между   доменными  объектами,  которые  отражают  наш  реальный  мир,  и  объектами   представления     [MarRn  Fowler]  
  • 6.
  • 7.
    View   @POST @Path("/add") publicUser add(String name) { return find(dao.insert(name)); } @GET @Path("/{id}") public User find(@PathParam("id") Integer id) { return dao.findById(id); } @GET @Path("/all") public List<User> all(@PathParam("id") Integer id) { return dao.all(); }
  • 8.
    Domain   public classUser { @JsonProperty public final int id; @JsonProperty public final String name; public User(int id, String name) { this.id = id; this.name = name; } }
  • 9.
    Data  Access  (JDBI)   @RegisterMapper(UserMapper.class) public interface UserDAO { @SqlUpdate("insert into user (name) values (:name)") @GetGeneratedKeys int insert(@Bind("name") String name); @SqlQuery("select * from user where id = :id") User findById(@Bind("id") int id); @SqlQuery("select * from user") List<User> all(); }
  • 10.
    Усложняем  бизнес  логику   •  Пользователь  всегда  принадлежит  определенной  группе     (нужно  указывать  группу  при  создании)     •  В  группу  может  входить  не  более  5   пользователей     (необходима  проверка)     •  Куда  разместим  эту  логику  ?  
  • 11.
    Очевидное  решение     Вся  логика  на  контроллере   @POST @Path("/add") public Response add(User user) { Integer count = userDAO.findCountUsersByGroup(user.groupId); if (count == 5) { return status(BAD_REQUEST) .entity("More users than allowed in group") .type("text/plain") .build(); } return ok(find(userDAO.insert(user.name, user.groupId))) .build(); }
  • 12.
    И  чтение   @GET@Path("/{userId}") public User find(@PathParam("userId") Integer userId) { User user = userDAO.findById(userId); user.setGroup(groupDAO.findGroupById(user.groupId)); return user; } @Timed @GET @Path("/all") public List<User> all() { List<User> users = userDAO.all(); users.stream().forEach (u -> u.setGroup(groupDAO.findGroupById(u.groupId))); return users; }
  • 13.
    FSUC   Fat  Stupid  Ugly  Controllers   [Pádraic  Brady  (Zend  Framework)]     ТТУК  -­‐  Толстые  тупые  уродливые  контроллеры     Среднестатистический  ТТУК  получал  данные  из  БД  (используя  уровень  абстракции   базы  данных,  делая  вид,  что  это  модель)  или  манипулировал,  валидировал,   записывал,  а  также  передавал  данные  в  Вид     Такой  подход  очень  популярен    Контроллер  -­‐  Модель  мутант  
  • 14.
    FSUC  –  Добро  или  зло  ?   •  Толстый  (содержащий  модель)  контроллер  практически  не  поддается  переносу  и   тестированию  (TDD)   –  мы  не  можем  создать  экземпляр  класса  контроллера  вне  фреймворка,  контроллер  жестко   привязан  к  определенному  фреймворку   –  Вид  должен  по  возможности  избегать  контакта  с  Контроллером  и  получать  данные   напрямую  из  модели   •  Но,  мне  не  надо  думать  куда  разместить  бизнес  логику   •  И  один  класс  на  все  случаи  жизни.  Что  может  быть  проще  !    
  • 15.
    Альтернативы   •  Переносим  логику  в  выделенный  сервис   –  Процедурный  стиль  программирования   •  Переносим  логику  в  доменный  объект   –  Объектно-­‐ориентированный  стиль    
  • 16.
    Процедурный  стиль   @POST@Path("/add") @Consumes(MediaType.APPLICATION_JSON) public Response add(User user) { try { return ok(service.insert(user)).build(); } catch (RuntimeException e) { return status(BAD_REQUEST).entity(e.getMessage()) .type("text/plain") .build(); } } public User insert(final User user) { Integer count = userDAO.findCountUsersByGroup(user.groupId); if (countUsersByGroup == 5) { throw new IllegalStateException("More users than allowed"); } return (find(userDAO.insert(user.name, user.groupId))); } Как   Назвать   Сервис   ?  
  • 17.
    ООП   •  В  каком    сервисе  искать  необходимый  нам  метод  ?       •  Очень  часто  на  руках  у  нас  имеется  только  доменный  объект   •  Не  проще  ли  вызвать  метод  имеющегося  на  руках  доменного  объекта  ?   •  Ясный  код  при  котором  не  нужно  помнить,  что  куда  положил   public User add(User user) { return user.insert(); } public User add(User user) { return service.insert(user); }
  • 18.
    Однако   ООП  порождает  много  больше  вопросов,  чем  старый  добрый   процедурный  подход   1.  Как  из  доменных  объектов  дотянутся  до  DAO   2.  Как  расправиться  с  циклическими  зависимостями     DAO  объекты  ß  à  Доменные  объекты   3.  Куда  разместить  методы  не  относящиеся  к  доменным   объектам   Снова  службы  ?  
  • 19.
    Как  из  доменных  объектов  дотянутся  до   DAO   1.  Радикальное  решение  –  втянуть  DAO  объекты  в  доменные     (AcRve  Record)   Замечательная  сцепленность   Нет  класса  –  нет  проблемы     А  как  насчет  SRP  ?   2.  Singleton   Антипатерн   3.  Service  Locator   Антипатерн   4.  Обращение  зависимости  (DIP)   1.  Передача  через  параметры   2.  Внедрение  зависимостей  (Dependency  InjecRon)   5.  Комбинации  вышеприведенных  подходов    )  
  • 20.
    Богатый  (Rich)  доменный    объект  и   Service  Locator   public User insert() { UserDAO userDAO = service(UserDAO.class); Integer count = userDAO.findCountUsersByGroup(groupId); if (count == 5) { throw new IllegalStateException("More users than allowed"); } this.id = userDAO.insert(name, groupId); return this; }
  • 21.
    Все  те  же  и  ленивая  (отложенная)   инициализация   Lazy Initialization @JsonProperty public String getGroupName() { if (groupName == null) { GroupDAO groupDAO = service(GroupDAO.class); groupName = groupDAO.findGroupById(groupId).name; } return groupName; }
  • 22.
    Бизнес  сервисы    ООП   •  Бывают  случаи  когда  метод  не  привязывается  к  доменному   объекту     – В  качестве  параметра  не  передается  доменный  объект   – В  качестве  параметра  передается  несколько  доменных   объектов,  и  сложно  предпочесть  один  из  них   – Логика  метода  сложна  и  (или)  опциональна  и  привязка  к   доменному  объекту  нарушает  сразу  множество  принципов  )   •  В  этих  случаях  формируются  бизнес  сервисы  (уровня  домена),   и  методы  переносятся  в  них    
  • 23.
    Бизнес  сервисы  DDD   •  Некоторые  бизнес  сервисы  типичны  и  с  целью   повышения  ясности  системы  могут  быть   классифицированы   •  Примеры  из  практики  «Проектирование  ведомое  бизнес   логикой»  DDD   – Фабрика  –  сервис  создающий  новый  экземпляр  класса   – Репозиторий  –  сервис  предоставляющий  доступ  к   коллекции  экземпляров  определенного  класса         [Eric  Evans]  
  • 24.
    Пример  репозитория   publicclass UserRepository implements Repository<User> { private final UserDAO userDAO; public UserRepository() { this.userDAO = service(UserDAO.class); } @Override public User find(final Integer userId) { return userDAO.findById(userId); } @Override public List<User> findAll() { return userDAO.all(); } }
  • 25.
    Упорядочиваем  связи   1. Обращаем  зависимости   2.  Вводим  интерфейсы   3.  Вводим  DTO  
  • 26.
  • 27.
  • 28.
    Объект  передачи  данных   •  Теперь  наши  доменные  объекты  выступают  в  двух  ипостасях   –  Как  богатые  доменные  объекты  содержащие  бизнес  логику   –  Как  объекты  передачи  данных   •  Смешение  ответственности  приводят  к  проблемам   –  Валидность  доменной  модели  (все  ли  поля  заполнены  при  передаче)   –  Противоречивые  требования  приводят  к  компромиссам   –  Объект  передачи  данных,  чаще  всего,    должен  быть  оптимизирован   под  потребности  производительности   –  Доменный  объект,  оптимизируется  под  повторное  использование,   модифицируемость  и  ясность   •  Из  хорошего  –  не  надо  перекладывать  данные  в  домен  
  • 29.
    Передаем  данные  в  систему   (альтернативы)   •  Передаем  данные  по  элементам  (декапсуляция)   –  Плюс,  высокая  производительность   –  Еще  один  плюс,    низкая  связанность   –  Минус,  при  передаче  сложных  структур  полностью  теряется  ясность   –  Минус,  сложнее  согласовывать  протокол  передачи  и  проводить  валидацию  данных     •  Создаем  объект  передачи  данных  (DTO)   –  Оптимизирован  под  производительность  (все  за  один  раз)   –  Оптимизирован  под  целостность  (строго  типизированные  данные)   –  Оптимизирован  под  модифицируемость  (версионность,    обратная  совместимость)       public User add(User user) {
  • 30.
    Вводим  DTO  public class UserDTO { @JsonProperty public int id; @JsonProperty public String name; @JsonProperty public int groupId; @JsonProperty public String groupName; public UserDTO() { } public User assemble() { return User.make(id, name, groupId); } public static UserDTO from(final User user) { UserDTO dto = new UserDTO(); dto.id = user.getId(); dto.name = user.getName(); dto.groupId = user.getGroup().getGroupId(); dto.groupName = user.getGroup().getName(); return dto; } } Бонусы     •  Можем  передавать  только   необходимую  информацию   (возможно,  собрав  ее  из   разных  источников)     •  Можем  принимать  только  то,   что  есть  в  удаленной  системе   (одним  пакетом,  без  лишних,   или  пустых  полей)    
  • 31.
    Усложняем  бизнес  логику   •  Добавим  функцию  перехода  пользователя  из  группы  в  группу   •  Что  передаем  на  наш  контроллер  ?     •  Очевидное  решение  UserDTO  и  GroupDTO   – Необходимо  протягивать  эти  данные  через  клиента   – Усложняется  клиент   – Усложняется  интерфейс  взаимодействия  
  • 32.
    Как  получить  доменный  объект     •  На  руках  только  идентификаторы  !   •  Очевидное  решение  –  Репозиторий   – Множество  избыточных  запросов  к  базе.  Медленно       final User user = repository.find(userId); user.moveTo(groupId);
  • 33.
    Альтернативные  варианты  получения   доменного  объекта     •  Протянуть  его  через  DTO   – Сложно   •  Держим  в  памяти  (например  кешируем  в  репозитории)   – Проблема  с  распределенными  системами   final User user = repository.find(userId); user.moveTo(groupId); final User user = dto.assemble() user.moveTo(groupId);
  • 34.
    Оптимизация     (Уменьшение  количества  запросов)   •  На  чтение   – Кэширование   – Связывание  (вытаскиваем  как  можно  больше  данных  одним   запросом)   – JOIN   – UNION       •  На  запись   – Пакетирование   – Единица  работы  (Unit  of  Work)  
  • 35.
    И  напоследок   напутствие  от  Pádraic:     Думайте  товарищи,  желание  думать  должно  быть   основным,  спрашивайте  везде,  анализируйте,  тестуйте,   когда  что-­‐то  вам  кажется  не  разумным.     Если  вы  все  принимаете  на  чистую  веру,  значит  вы   заслуживаете  того,  что  получаете