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