Если вы похожи на меня, в своей карьере вы встретили архитекторов, которые хотят объединить каждое приложение в компании: от самого маленького веб-приложения до самого большого. Все проекты должны соответствовать 542-страничному Внутреннему архитектурному руководству и разработать 6-уровневое приложение (это может быть 5, 6, 7 или 8 уровней, похоже, что архитекторы платят за уровень). Это не верно. Фактически, вы должны следовать старой доброй поговорке: правильный инструмент для правильной работы или правильная архитектура для правильных потребностей проекта . И вы можете пойти еще дальше: правильный язык для правильного применения и так далее. Из-за моего опыта работы с Java EE я придерживаюсь Java EE 7 в этом посте ( интерфейс JSF , EJBs услуги, конечные точки 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 соответственно. Это выглядит так:
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 становится центральной точкой ваших внешних вызовов, имеет дело с 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.