Одностраничные приложения быстро завоевывают популярность как способ реализации многофункциональных, надежных и мобильных веб-приложений. По сути, это требует изменения архитектуры приложения, когда весь пользовательский интерфейс приложения реализован с использованием 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 — более достижимой.

