Статьи

Платформа CUBA: TypeScript SDK и REST API

В этой статье мы поговорим о функции платформы CUBA, которая существует довольно давно, но до сих пор широко не известна — генератор интерфейсных SDK , и посмотрим, как она работает с аддоном REST API CUBA.

Java + JavaScript — брак, рожденный в сети

Только восемь лет назад мы, разработчики Java, использовали JavaScript в качестве языка «второго сорта» в наших веб-приложениях. В те дни его целью было добавление некоторой динамики к веб-страницам, созданным на стороне сервера, с такими фреймворками, как JSF, Struts, Tapestry или Thymeleaf. В настоящее время мы являемся свидетелями появления JavaScript как языка номер один для разработки на стороне клиента с такими фреймворками, как React, Vue или Angular, а с Node.js он даже доходит до серверной части.

На самом деле мы разрабатываем веб-приложения, которые могут использовать разные языки на разных уровнях: JavaScript для пользовательского интерфейса на стороне клиента, Java для обработки бизнес-логики, SQL для извлечения данных из базы данных, Python для анализа данных и т. Д. И мы должны объединить все эти языки в одно приложение, используя различные технологии. Самый распространенный пример — REST API. Основанный на независимом от платформы протоколе HTTP и простом формате JSON, теперь это метод по умолчанию для сшивания JS на стороне клиента и Java на стороне сервера.

Но даже самый лучший стежок не может быть бесшовным. Всегда существует проблема с определением API: какие методы вызывать, что такое модель данных и нужно ли передавать адрес улицы в виде структурированного объекта или просто в виде строки.

Как мы можем помочь нашим коллегам по JavaScript быстрее создать свой код и избежать недопонимания?

Swagger — окончательный ответ?

« Swagger » вы говорите, и вы правы. Swagger является де-факто промышленным стандартом для проектирования, построения, документирования и использования REST API. Существует ряд генераторов кода, которые помогают в создании клиентских SDK для разных языков.

CUBA Framework поддерживает Swagger, каждое приложение с надстройкой REST API имеет конечную точку, которая позволяет загружать документацию Swagger в формате .json или .yaml. Вы можете использовать эти файлы для генерации JS-клиента.

Пожалуйста, примите во внимание тот факт, что Swagger является только инструментом документации API. Но какую информацию разработчик внешнего интерфейса хочет видеть в API? «Классический» подход: сопоставить бизнес-функции с сервисами и создать четко определенный API. Затем представьте его как набор служб REST, добавьте документацию Swagger и наслаждайтесь.

Тогда почему GraphQL поражает тенденции, вызывающие ажиотаж среди разработчиков переднего плана? И обратите внимание, что доля GraphQL в мире веб-API растет. Что здесь происходит? Оказалось, что иногда проще дать интерфейсным разработчикам немного более «универсальный» API, чтобы избежать создания множества маленьких API для вариантов использования, которые могут часто меняться. Например, для вашей корзины покупок в веб-интерфейсе вам нужен сначала заказ с ценами, затем заказ с итогами и т. Д. GraphQL также является отличным инструментом, позволяющим избежать как перегрузок, так и неполадок, а также запрашивать несколько API одновременно, чтобы получить сложная структура данных.

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

И есть еще одна проблема, которую не решают ни Swagger, ни GraphQL, ни OData — что делать с сгенерированным клиентским кодом, если что-то изменилось. Прямая одноразовая генерация кода проста, но поддержка это другое дело. Как мы можем быть уверены, что наше интерфейсное приложение не выйдет из строя после удаления свойства объекта?

Итак, чтобы ускорить разработку интерфейса и упростить взаимодействие между командой и командой переднего плана, нам необходимо:

  1. Предоставить как специфичный для бизнеса, так и общий API
  2. Генерация внешнего кода на основе внутренней модели данных и сигнатур методов
  3. Изменить сгенерированный код с минимальными усилиями и потенциальными ошибками

Мы сталкиваемся со всеми этими проблемами в CUBA с помощью надстройки REST API и внешнего SDK-генератора.

CUBA TypeScript SDK

В CUBA надстройка API REST предоставляет следующие функциональные возможности:

  • CRUD-операции над моделью данных
  • Выполнение предопределенных JPQL-запросов
  • Выполнение методов обслуживания
  • Получение метаданных (сущности, представления, перечисления, типы данных)
  • Получение текущих полномочий пользователя (доступ к сущностям, атрибутам, конкретным разрешениям)
  • Получение текущей информации о пользователе (имя, язык, часовой пояс и т. Д.)
  • Работа с файлами

Таким образом, у нас есть все необходимое для работы с приложением с любого клиентского интерфейса. Все эти API описаны в файле swamger YAML или JSON , так что вы можете сразу приступить к реализации приложения.

Очень важно установить правила безопасности для пользователей REST API, чтобы предотвратить случайное воздействие на конечную точку всех пользователей. Во-первых, запретите общий доступ к REST API для всех пользователей, а затем создайте специальные разрешения для ролей, которым требуется доступ к нужным функциям.

Но CUBA предлагает больше, чем просто REST API. Вы можете создать SDK, который можно использовать в качестве основы для любой среды разработки интерфейса: React, Angular, Vue или другой.

С помощью генератора вы создаете набор классов TypeScript, который позволяет вам вызывать CUBA API из вашего клиентского приложения.

Чтобы создать SDK, вам просто нужно запустить

1
npm install -g @cuba-platform/front-generator

А потом

1
gen-cuba-front sdk:all

и все классы будут созданы для вас. Вы даже можете создать простой пользовательский интерфейс на основе ReactJS, чтобы ваши клиенты могли сразу же начать работать с приложением на основе CUBA. Пользовательский интерфейс довольно прост, но с CUBA вы сразу получите все функции, включая аутентификацию, доступ к данным на основе ролей, получение графа сущностей и т. Д.

Давайте подробнее рассмотрим, что есть в SDK.

Модель данных

Модель данных приложения представлена ​​в виде набора классов TypeScript. Если мы посмотрим на приложение Session Planner, используемое в QuickStart , то здесь есть сущность:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
@NamePattern("%s %s|firstName,lastName")
@Table(name = "SESSIONPLANNER_SPEAKER")
@Entity(name = "sessionplanner_Speaker")
public class Speaker extends StandardEntity {
   @NotNull
   @Column(name = "FIRST_NAME", nullable = false)
   protected String firstName;
 
   @Column(name = "LAST_NAME")
   protected String lastName;
 
   @Email
   @NotNull
   @Column(name = "EMAIL", nullable = false, unique = true)
   protected String email;
//Setters and getters here
}

И в SDK мы получим класс:

1
2
3
4
5
6
export class Speaker extends StandardEntity {
   static NAME = "sessionplanner_Speaker";
   firstName?: string | null;
   lastName?: string | null;
   email?: string | null;
}

Все ассоциации и композиции будут сохранены, поэтому вы сможете извлекать граф сущностей вместо того, чтобы извлекать сущности один за другим, используя несколько вызовов API.

Больше нет DTO — вы получите точно такие же данные, как описано на сервере.

Бизнес-услуги

Все сервисы, предоставляемые через REST в CUBA, будут иметь представление TypeScript в SDK. Например, если мы представим Session Service с использованием REST API, вы получите код TypeScript, который выглядит следующим образом:

1
2
3
4
5
6
7
export var restServices = {
   sessionplanner_SessionService: {
       rescheduleSession: (cubaApp: CubaApp, fetchOpts?: FetchOptions) => (params: sessionplanner_SessionService_rescheduleSession_params) => {
           return cubaApp.invokeService("sessionplanner_SessionService", "rescheduleSession", params, fetchOpts);
       }
   }
};

Поэтому вы сможете вызвать его из пользовательского интерфейса, просто написав следующую строку:

1
2
3
restServices.sessionplanner_SessionService.rescheduleSession(cubaREST)({session, newStartDate}).then( (result) => {
   //Result handling
});

Удобно, не правда ли? Вся рутинная работа сделана для вас.

Общий API

Если вам нужно реализовать пользовательскую логику только для внешнего интерфейса, вы всегда можете использовать набор функций, определенных в базовой библиотеке REST платформы CUBA, таких как:

1
2
loadEntities<T>(entityName: string, options?: EntitiesLoadOptions, fetchOptions?: FetchOptions): Promise<Array<SerializedEntity<T>>>;
deleteEntity(entityName: string, id: any, fetchOptions?: FetchOptions): Promise<void>;

Эти функции предоставляют вам детальный доступ к операциям CRUD с сущностями в приложении. Безопасность по-прежнему сохраняется, CUBA проверяет все неанонимные вызовы на стороне сервера и предотвращает выборку объектов или атрибутов, которые не соответствуют роли пользователя.

1
2
3
cubaREST.loadEntities<Speaker>(Speaker.NAME).then( (result => {
 //Result handling
}));

Используя этот универсальный API, разработчик может создать приложение JS с пользовательским уровнем API, созданным поверх универсального CRUD, и развернуть его на сервере node.js, реализуя архитектурный шаблон « backend for frontend ». Более того, при таком подходе может быть реализовано более одного уровня API, мы можем реализовать различный набор API для разных клиентов: ReactJS, Native iOS и т. Д. Фактически сгенерированный SDK является идеальным инструментом для этого варианта использования.

Что не очень хорошо в универсальном API, так это риск недостаточной или избыточной выборки данных, когда вы либо получаете больше атрибутов, чем нужно, либо у вас недостаточно атрибутов в дескрипторах API. Entity Views CUBA решают эту проблему на бэкэнде, и мы предоставляем такую ​​же опцию для фронтенд-разработчиков! Для каждого сгенерированного класса TypeScript мы создаем типы, которые отражают представления:

1
2
3
4
5
6
7
export type SpeakerViewName = "_minimal" | "_local" | "_base";
 
export type SpeakerView<V extends SpeakerViewName> =
V extends "_minimal" ? Pick<Speaker, "id" | "firstName" | "lastName"> :
V extends "_local" ? Pick<Speaker, "id" | "firstName" | "lastName" | "email"> :
V extends "_base" ? Pick<Speaker, "id" | "firstName" | "lastName" | "email"> :
never;

Таким образом, вы можете получить сущность из бэкэнда, и вы получите только указанные атрибуты. Следовательно, вам не нужно угадывать, какие атрибуты были выбраны. И IDE поможет вам с автозаполнением кода.

Обновления API

Как уже упоминалось, генерация кода — это даже не половина работы разработчиков. Изменение и поддержка кода — вот где большинство усилий. Генератор TypeScript SDK CUBA анализирует код во время последующих запусков, отслеживает изменения и обновляет их постепенно. А компилятор TypeScript гарантирует, что вы не забудете обновить свой пользовательский код, использующий SDK, если вы используете TypeScript в качестве основного языка разработки во внешнем интерфейсе.

Вывод

Если вы хотите разработать клиентский пользовательский интерфейс на основе JS (React / React Native или Angular или Vue) для приложения CUBA в дополнение к универсальному пользовательскому интерфейсу, вы можете использовать надстройку REST API и TypeScript SDK. Какую бы технологию вы не решили использовать, вы можете сосредоточиться на дизайне или производительности, чтобы обеспечить лучший пользовательский опыт вместо выполнения рутинных задач кодирования. И вы можете быть уверены, что взаимодействие JS с Java, а также поддержка смены API-интерфейсов будут наименьшей из ваших проблем.

Опубликовано на Java Code Geeks с разрешения Андрея Беляева, партнера нашей программы JCG . Смотреть оригинальную статью здесь: Платформа CUBA: TypeScript SDK и REST API

Мнения, высказанные участниками Java Code Geeks, являются их собственными.