Одностраничные приложения быстро завоевывают популярность как способ реализации многофункциональных, надежных и мобильных веб-приложений. По сути, это требует изменения архитектуры приложения, когда весь пользовательский интерфейс приложения реализован с использованием JavaScript, а код на стороне сервера предоставляет RESTful, основанный на JSON API для логики приложения на стороне сервера и доступа к данным. Эта модель показана ниже:
Случай для отдельных клиентских и серверных компонентов развертывания
Этот сдвиг SPA обеспечивает пользовательский опыт и преимущества в производительности, а также дает возможность полностью отделить пользовательский интерфейс от логики на стороне сервера. Отделение пользовательского интерфейса от логики приложения — это то, что мы делаем с точки зрения разделения кода, применяя шаблон Model View Controller (MVC). С точки зрения жизненного цикла приложения развертывания они по-прежнему связаны, то есть приложение упаковано и развернуто со статическими элементами на стороне клиента и элементами на стороне сервера в одном компоненте.
Кажется, что естественный инстинкт состоит в том, чтобы упаковать клиентские и серверные элементы в один компонент JEE WAR. Это может упростить жизненный цикл приложения, однако конструкция приложения, похоже, естественным образом организует разработчиков, работающих над пользовательским интерфейсом, и разработчиков, работающих на серверном API, и даже больше, поскольку используются два разных языка разработки. Таким образом, вместо одной WAR разделение приложений на отдельные развертываемые WAR для элементов пользовательского интерфейса и API на стороне сервера может обеспечить следующие преимущества:
- API остается стабильным для разработки пользовательского интерфейса (не движущаяся цель)
- Пользовательский интерфейс контролирует внесение изменений API на стороне сервера
- Поддерживает параллельные пути разработки уровней пользовательского интерфейса и API
- Изменения в пользовательском интерфейсе можно протестировать и перенести в QA и производственную среду без повторного тестирования уровня API.
- Базовая реализация / технология API может быть изменена без влияния на пользовательский интерфейс
- Реализация / технология пользовательского интерфейса может измениться, не влияя на API
- Возможность вводить элементы пользовательского интерфейса во время выполнения (использует динамическое поведение JavaScripts)
Вот картина этой топологии:
Как?
Поскольку пользовательский интерфейс реализован с использованием динамического JavaScript, компонент JEE WAR необязательно использовать для размещения ресурсов пользовательского интерфейса. Можно использовать любой веб-сервер, такой как Apache или очень популярный сервер Node.js. Тем не менее, предприятия, которые уже поддерживают JEE, будут иметь поддержку жизненного цикла для WAR, и она будет открыта для использования динамического поведения на стороне сервера для начальной загрузки ресурсов, аутентификации и динамической интеграции или посредничества.
Например, вместо начальной загрузки SPA с помощью index.html, index.jsp может использоваться для применения некоторой пользовательской / клиентской логики к процессу загрузки.
Сервлет Решение
Одним из решений поддержки API / конечных точек SPA является реализация сервлета в статическом контенте SPA WAR, который перенаправляет URL-маршруты API на сервер, где находится конечная точка. Это достигается путем определения сервлета в файле web.xml с отображением вызовов API на сервер.
Вот пример конфигурации web.xml, которая обрабатывает URI, начинающиеся с API:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
<servlet> <servlet-name>api</servlet-name> <display-name>api</display-name> <servlet-class>com.khs.spa.servlet.ApiServlet</servlet-class> <init-param> <param-name>redirect</param-name> <param-value>localhost:8080/khs-command-ref</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>api</servlet-name> <url-pattern>/api/*</url-pattern> </servlet-mapping> |
Сервлет будет перенаправлять на API WAR (ы) на основе URL-адреса, определенного в значении параметра инициализации перенаправления, показанном выше.
Реализация сервлета API, которая перенаправляет запросы HTTP HTTP GET / POST / PUT / DELETE API, показана ниже:
|
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
57
58
59
60
61
62
63
64
65
|
package com.khs.spa.servlet;import java.io.IOException;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;public class ApiServlet extends HttpServlet { private static final long serialVersionUID = 4345668988238038540L; private String redirect = null; @Override public void init() throws ServletException { super.init(); // load redirect for servlet redirect = getServletConfig().getInitParameter("redirect"); if (redirect == null) { throw new RuntimeException("redirect value not set in servlet <init-param>"); } } private void doService(HttpServletRequest request, HttpServletResponse response) throws RuntimeException, IOException { // you could do extra stuff here, i.e. logging etc... String path = request.getRequestURI().split(request.getContextPath())[1]; String route = redirect + path; response.sendRedirect(route); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doService(request, response); } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doService(request, response); } @Override protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doService(request, response); } @Override protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doService(request, response); } @Override protected long getLastModified(HttpServletRequest req) { return super.getLastModified(req); } @Override protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { super.doHead(req, resp); } @Override protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { super.doOptions(req, resp); } @Override protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { super.doTrace(req, resp); }} |
Соображения
Этот подход предполагает реализацию API без сохранения состояния. Поскольку происходит перенаправление, если API WAR WAR основан на сеансах, оно не будет работать, если не установлен какой-либо механизм федеративного сеанса. Механизмы аутентификации и авторизации могут происходить на клиентском UI-WAR SPA и / или на уровне API. Аналогичным образом, если для SPA требуется доступ к нескольким службам API или корпоративным системам, они все равно могут быть применены в UI-WAR SPA.
Одностраничные приложения не только позволяют нам реализовывать богатые / отзывчивые пользовательские интерфейсы, но и способствуют использованию легких, простых в использовании релевантных API для данных и логики приложения. Эта физическая развязка пользовательского интерфейса во время выполнения делает концепцию «одноразового» пользовательского интерфейса более реалистичной, а доступность повторно используемых сервисов через уровень API — более достижимой.

