Статьи

Несколько архитектурных стилей с Java EE 7

Если вы похожи на меня, в своей карьере вы встретили  архитекторов,  которые хотят объединить каждое приложение в компании: от самого маленького веб-приложения до самого большого. Все проекты должны соответствовать  542-страничному Внутреннему архитектурному руководству и разработать 6-уровневое приложение (это может быть 5, 6, 7 или 8 уровней, похоже, что архитекторы платят за уровень). Это не верно. Фактически, вы должны следовать старой доброй поговорке: правильный инструмент для правильной работы или  правильная архитектура для правильных потребностей проекта . И вы можете пойти еще дальше: правильный язык для правильного применения и так далее. Из-за моего опыта работы с Java EE я придерживаюсь  Java EE 7  в этом посте (  интерфейс JSFEJBs услуги,   конечные точки JAX-RS и  объекты JPA ). Итак, сколько архитектурных стилей вы можете создать с помощью Java EE 7? Бесконечный; о)

Я пишу этот блог, потому что я показывал код, сгенерированный  JBoss Forge,  коллеге, и он сказал: « Forge не создает 5-уровневое веб-приложение »… и  что с того  ? В этом посте я объясню код по умолчанию, сгенерированный  JBoss Forge  (который я называю Horizontal Service Style) и другие варианты, которые вы можете создать из него.

Стиль горизонтальных услуг

Когда  JBoss Forge  генерирует веб-приложение JSF с интерфейсом REST, и компоненты поддержки JSF, и конечные точки JAX-RS используют EntityManager для работы с объектами JPA. Архитектурный стиль выглядит следующим образом:

Горизонтальные Услуги

Я называю этот  стиль горизонтальными службами,  потому что если вам нужно добавить интерфейс веб-службы SOAP, вы напишите aBookSOAP, который также будет использовать EntityManager и напрямую вызывать объекты. Чтобы иметь представление о динамике в коде, я покажу вам часть операции CRUD как на компоненте поддержки JSF, так и на конечной точке REST:


JSF BookBean
@Named
@Stateful
@ConversationScoped
public class BookBean implements Serializable {
// ...
@Inject
private Conversation conversation;
@PersistenceContext(unitName = "sampleJavaEEHorizontalPU", type = PersistenceContextType.EXTENDED)
private EntityManager em;
// ...
public Book findById(Long id) {
return em.find(Book.class, id);
}
public String update() {
conversation.end();
try {
if (id == null) {
em.persist(book);
return "search?faces-redirect=true";
} else {
em.merge(book);
return "view?faces-redirect=true&id=" + book.getId();
}
} catch (Exception e) {
FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(e.getMessage()));
return null;
}
}
public String delete() {
conversation.end();
try {
Book deletableEntity = findById(getId());
em.remove(deletableEntity);
em.flush();
return "search?faces-redirect=true";
} catch (Exception e) {
FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(e.getMessage()));
return null;
}
}
}

Этот компонент поддержки имеет дело с навигацией (каждый метод возвращает строку, которая является именем страницы, к которой нужно перейти) и создает, удаляет, обновляет сущность Book. Теперь, если вы посмотрите на конечную точку REST, вы увидите, что обязанности довольно схожи: BookEndpoint создает, удаляет, обновляет сущность Book и возвращает aResponse. Это код BookEndpoint:


JAX-RS BookEndpoint
@Transactional
@Path("/books")
public class BookEndpoint {
// ...
@PersistenceContext(unitName = "sampleJavaEEHorizontalPU")
private EntityManager em;
// ...
@GET
@Path("/{id:[0-9][0-9]*}")
@Produces("application/xml")
public Response findById(@PathParam("id") Long id) {
TypedQuery findByIdQuery = em.createQuery("SELECT DISTINCT b FROM Book b LEFT JOIN FETCH b.authors WHERE b.id = :entityId ORDER BY b.id", Book.class);
findByIdQuery.setParameter("entityId", id);
Book entity = findByIdQuery.getSingleResult();
if (entity == null) {
return Response.status(Status.NOT_FOUND).build();
}
return Response.ok(entity).build();
}
@PUT
@Path("/{id:[0-9][0-9]*}")
@Consumes("application/xml")
public Response update(Book entity) {
em.merge(entity);
return Response.noContent().build();
}
@DELETE
@Path("/{id:[0-9][0-9]*}")
public Response deleteById(@PathParam("id") Long id) {
Book deletableEntity = em.find(Book.class, id);
if (deletableEntity == null) {
return Response.status(Status.NOT_FOUND).build();
}
em.remove(deletableEntity);
return Response.noContent().build();
}
}

Как видите, конечная точка REST использует новую аннотацию @Transactional из Java EE 7, поэтому она может обрабатывать EntityManager транзакционным способом. Так что если вы похожи на моего коллегу и вам не нравится этот архитектурный стиль, вот что я думаю о плюсах и минусах:

преимущества

  • Это относительно плоская архитектура. У вас нет никаких дополнительных слоев, интерфейсов или шаблона UnlessAbstractSomethingFactory.
  • Очень быстро развиваться, вы развиваетесь в соответствии со своими потребностями
  • Идеально подходит для простых приложений (без сложной бизнес-логики)

Недостатки

  • Нет  разделения интересов , один класс выполняет несколько задач (например, BookBean управляет навигацией JSF, а также обрабатывает объекты)
  • Дублирование кода Метод findById реализован одинаково как в BookBean, так и в BookEndpoint.
  • Тяжелый рефакторинг. Если ваше приложение усложняется, вам может понадобиться рефакторинг и переход на архитектурный стиль EJB Centric.

EJB Centric Style

Это наиболее распространенный архитектурный стиль: он использует разделение интересов. Уровень EJB имеет дело с EntityManager и другой сложной бизнес-логикой, в то время как BookBean и BookEndpoint имеют дело только с проблемами JSF и REST соответственно. Это выглядит так:

EJB Centric
BookEJB — это сессионный компонент без сохранения состояния, который осуществляет доступ ко всей базе данных и координирует другие внешние службы. Код выглядит так:


BookEJB
@Stateless
public class BookEJB {
@PersistenceContext(unitName = "sampleJavaEEEJBCentricPU")
private EntityManager em;
// ...
public Book findById(Long id) {
return em.find(Book.class, id);
}
public void update(Book entity) {
em.merge(entity);
}
public void delete(Book deletableEntity) {
em.remove(em.merge(deletableEntity));
}
}

Как BookBean, так и BookEndpoint вводят BookEJB и делегируют все управление сущностями. Конечная точка REST может выглядеть так:


BookEndpoint
@Path("/books")
public class BookEndpoint {
@Inject
private BookEJB bookService;
// ...
@GET
@Path("/{id:[0-9][0-9]*}")
@Produces("application/xml")
public Response findById(@PathParam("id") Long id) {
Book entity = bookService.findByIdWithRelations(id);
if (entity == null) {
return Response.status(Status.NOT_FOUND).build();
}
return Response.ok(entity).build();
}
@PUT
@Path("/{id:[0-9][0-9]*}")
@Consumes("application/xml")
public Response update(Book entity) {
bookService.update(entity);
return Response.noContent().build();
}
@DELETE
@Path("/{id:[0-9][0-9]*}")
public Response deleteById(@PathParam("id") Long id) {
Book deletableEntity = bookService.findById(id);
if (deletableEntity == null) {
return Response.status(Status.NOT_FOUND).build();
}
bookService.delete(deletableEntity);
return Response.noContent().build();
}
}

Как вы можете видеть, BookEndpoint имеет дело со всеми взаимодействиями REST (построение ответа, статус ошибки …) и делегирует другие проблемы EJB. Я не показываю код BookBean, но он будет выглядеть очень похоже.

преимущества

  • Разделение проблем, каждый класс выполняет свою работу
  • Если вы добавляете интерфейс другого типа (скажем, SOAP Web Servies), вы снова используете слой EJB
  • Идеально подходит для сложных приложений
  • Весы лучше, чем в предыдущем архитектурном стиле

Недостатки

  • Добавляет дополнительный слой к приложению, но благодаря представлению  без интерфейса , в большинстве случаев вам даже не нужно добавлять интерфейс (так что это всего лишь один класс для добавления)

Rest Centric Style

Благодаря JAX-RS 2.0 у нас теперь есть клиентский API, что означает, что мы можем наконец вызвать стандартный веб-сервис REST. Затем мы можем поместить конечную точку REST в центр нашего приложения, и BookBean будет использовать клиентский API JAX-RS для ее вызова:

REST-ориентированный

В этой конфигурации конечная точка REST становится центральной точкой ваших внешних вызовов, имеет дело с EntityManager и выполняет всю сложную бизнес-логику. Код BookEndpoint выглядит аналогично тому, что я показал вам до сих пор. Интересная часть находится в BookBean, который широко использует клиентский API:


BookBean
@Named
@ConversationScoped
public class BookBean implements Serializable {
// ...
private Client client = ClientBuilder.newClient();
private WebTarget target;
@PostConstruct
private void setWebTarget() {
HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
String restEndointURL = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath() + "/rest/books/";
target = client.target(restEndointURL);
}
// ...
public Book findById(Long id) {
return target.path("{id}").resolveTemplate("id", id).request(MediaType.APPLICATION_XML).get(Book.class);
}
public String update() {
conversation.end();
try {
if (id == null) {
target.request().post(Entity.entity(book, MediaType.APPLICATION_XML));
return "search?faces-redirect=true";
} else {
target.request().put(Entity.entity(book, MediaType.APPLICATION_XML));
return "view?faces-redirect=true&id=" + book.getId();
}
} catch (Exception e) {
FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(e.getMessage()));
return null;
}
}
public String delete() {
conversation.end();
try {
target.path("{id}").resolveTemplate("id", getId()).request(MediaType.APPLICATION_XML).delete();
return "search?faces-redirect=true";
} catch (Exception e) {
FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(e.getMessage()));
return null;
}
}
}

Как видите, Client и WebTarget используются для выполнения операций GET, POST, PUT и DELETE на конечной точке REST.

преимущества

  • Обеспечивает RESTful стиль
  • Идеально, если у вас есть несколько внешних клиентов REST
  • Ешьте свою собачью еду (ваши бобы JSF — первые потребители ваших конечных точек REST)

Недостатки

  • Your JSF backing beans spend most of their time marshalling/unmarshalling the entities into/from XML (or JSon)

Conclusion

There is no good or bad architecture, there are several use cases and you can create the appropriate style for your needs. Here I just explain 3 different ways of writing the same thing with Java EE 7 but there are many more (another one would be to have several concerns into a single class, such as the Monster Component, another one is to create an extra (useless?) DAOlayer…) :

  • Horizontal Services : each service (JSF, REST, SOAP…) deals with the EntityManager, accesses the database and does the business logic
  • EJB Centric : each interface (JSF, REST, SOAP…) accesses an EJB layer that does most of the processing
  • REST Centric : the front views (JSF, Javascript…) access a REST layer that does all the processing

I hope JBoss Forge 2 will be able to easily generate such diversity, I’ll contribute to make it happen… feel free tojoin the discussion.

Any other style that you use ? What about the ones I’ve presented here, any other ideas ? Download the code and do not hesitate to contribute to it.