Статьи

Разделение ресурсов SPA и реализации API на отдельные компоненты WAR

Одностраничные приложения быстро завоевывают популярность как способ реализации многофункциональных, надежных и мобильных веб-приложений. По сути, это требует изменения архитектуры приложения, когда весь пользовательский интерфейс приложения реализован с использованием JavaScript, а код на стороне сервера предоставляет RESTful, основанный на JSON API для логики приложения на стороне сервера и доступа к данным. Эта модель показана ниже:

example1

Случай для отдельных клиентских и серверных компонентов развертывания

Этот сдвиг SPA обеспечивает пользовательский опыт и преимущества в производительности, а также дает возможность полностью отделить пользовательский интерфейс от логики на стороне сервера. Отделение пользовательского интерфейса от логики приложения — это то, что мы делаем с точки зрения разделения кода, применяя шаблон Model View Controller (MVC). С точки зрения жизненного цикла приложения развертывания они по-прежнему связаны, то есть приложение упаковано и развернуто со статическими элементами на стороне клиента и элементами на стороне сервера в одном компоненте.

Кажется, что естественный инстинкт состоит в том, чтобы упаковать клиентские и серверные элементы в один компонент JEE WAR. Это может упростить жизненный цикл приложения, однако конструкция приложения, похоже, естественным образом организует разработчиков, работающих над пользовательским интерфейсом, и разработчиков, работающих на серверном API, и даже больше, поскольку используются два разных языка разработки. Таким образом, вместо одной WAR разделение приложений на отдельные развертываемые WAR для элементов пользовательского интерфейса и API на стороне сервера может обеспечить следующие преимущества:

  • API остается стабильным для разработки пользовательского интерфейса (не движущаяся цель)
  • Пользовательский интерфейс контролирует внесение изменений API на стороне сервера
  • Поддерживает параллельные пути разработки уровней пользовательского интерфейса и API
  • Изменения в пользовательском интерфейсе можно протестировать и перенести в QA и производственную среду без повторного тестирования уровня API.
  • Базовая реализация / технология API может быть изменена без влияния на пользовательский интерфейс
  • Реализация / технология пользовательского интерфейса может измениться, не влияя на API
  • Возможность вводить элементы пользовательского интерфейса во время выполнения (использует динамическое поведение JavaScripts)

Вот картина этой топологии:

example2

Как?

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