Автор оригинала: Manuel Vila.
Современные приложения для полного стека – как однострачные приложения или мобильные приложения – обычно имеют шесть слоев
- доступ к данным
- Backend Model.
- API Server.
- API клиент
- Frontend Model.
- и пользовательский интерфейс.
При оформлении архитектуры вы можете достичь некоторых характеристик хорошо разработанного приложения, такого как Разделение опасений или Свободная муфта Отказ
Но это не приходит без недостатков. Обычно это происходит за счет других важных характеристик, таких как простота, Сплоченность и ловкость.
Кажется, мы не можем иметь все это все. Мы должны идти на компромисс.
Проблема в том, что разработчики обычно строят каждый слой как совершенно другой мир самостоятельно.
Даже если вы реализуете слои на одном языке, они не могут общаться друг с другом очень легко.
Вам нужно много Клейкий код подключить их все, а Доменная модель дублируется через стек. В результате ваша ловкость развития страдает резко.
Например, добавление простого поля к модели часто требуется изменение всех слоев стека. Это может чувствовать немного смешное.
Ну, я недавно много думал об этой проблеме. И я верю, что нашел выход.
Вот хитрость: Конечно, слои приложения должны быть «физически» отделены. Но им не нужно быть «логически» отделены.
Унифицированная архитектура
В объектно-ориентированном программировании, когда мы используем наследство, мы получаем несколько классов, которые можно увидеть двумя способами: физическими и логичными. Что я имею в виду под этим?
Давайте представим, что у нас есть класс B что наследует от класса A . Тогда А и B можно рассматривать как два физических класса. Но логично, они не разделены, а B можно рассматривать как логический класс, который составляет свойства А со своими свойствами.
Например, когда мы называем метод в классе, нам не нужно беспокоиться, если метод реализован в этом классе или родительском классе. С точки зрения абонента, о чем беспокоиться только один класс. Родитель и ребенок объединяются в единый логический класс.
Как насчет применения того же подхода к слоям приложения? Не было бы здорово, если, например, фантазия может каким-то наследовать от бэкэнда?
Итак, Frontend и Backend будут объединены в один логический слой. И это удалило бы все вопросы общения и обмена. Действительно, классы Backend, атрибуты и методы будут непосредственно доступны из интерфейса.
Конечно, мы обычно не хотим разоблачить целую бэкэнд к Frontend. Но то же самое касается наследства класса, и есть элегантное решение, которое называется «частные свойства». Аналогичным образом, бэкэнда может выборочно выбирать некоторые атрибуты и методы.
Возможность захватить все слои приложения из одного единого унифицированного мира – это не небольшая сделка. Это полностью меняет игру. Это как идет от 3D World в 2D World. Все становится намного проще.
Наследование не зла Отказ Да, это может быть неправильно использовано, а на некоторых языках это может быть довольно жестко. Но при правильном использовании он является бесценным механизмом на нашем панели инструментов.
У нас есть проблема, хотя. Насколько я знаю, нет языка, позволяющий нам наследовать классы в разных средах выполнения. Но мы программисты, не так ли? Мы можем построить все, что мы хотим, и мы можем продлить язык, чтобы предоставить новые возможности.
Но прежде чем мы доберемся до этого, давайте распустим стек, чтобы увидеть, как каждый слой может вписаться в единую архитектуру.
Доступ к данным
Для большинства приложений база данных может быть абстрактна с использованием своего рода ORM. Итак, от перспективы разработчиков нет ни одного слоя доступа к данным.
Для более амбициозных приложений, возможно, нам придется оптимизировать схемы базы данных и запросы. Но мы не хотим беспорядок в бэкэндской модели с этими проблемами, и это там, где может быть уместный дополнительный слой.
Мы создаем уровень доступа к данным для реализации концерн оптимизации, и это обычно происходит поздно в цикле разработки, если оно когда-либо произойдет.
Во всяком случае, если нам нужен такой слой, мы можем построить его позже. С пересложным наследством мы можем добавить слой доступа к данным сверху слоя модели Backend, практически без изменений в существующем коде.
Backend Model.
Как правило, слой бэкэнда обрабатывает следующие обязанности:
- Формирование модели домена.
- Реализация бизнес-логики.
- Обработка механизмов авторизации.
Для большинства бэкэндов это нормально для их реализации всех в одном слое. Но, если мы хотим справиться с некоторыми проблемами отдельно, например, мы хотим разделить авторизацию от бизнес-логики, мы можем реализовать их в двух слоях, которые наследуют друг от друга.
API слои
Чтобы подключить интерфейс и бэкэнда, мы обычно создаем веб-API (RECT, GRAGHQL и т. Д.), И это усложняет все.
Веб-API должен быть реализован с обеих сторон: клиент API в интерфейсе и сервере API на бэкэнде. Это два дополнительных слоя для беспокойства, и обычно приводит к дублированию всей модели домена.
Веб-API – это не что иное, как код клея, и это боль в заднице, чтобы построить. Итак, если мы сможем избежать этого, это огромное улучшение.
К счастью, мы можем воспользоваться пересложным наследством снова. В единой архитектуре нет веб-API для построения. Все, что нам нужно сделать, это унаследовать модель Frontend из модельной модели, и мы сделаем.
Тем не менее, есть еще хорошие случаи использования для создания веб-API. Вот когда нам нужно разоблачить бэкэнд к некоторым сторонним разработчикам или когда нам нужно интегрировать с некоторыми устаревшими системами.
Но давайте честным, большинство приложений не имеют такого требования. И когда они это делают, легко обрабатывать его потом. Мы можем просто реализовать веб-API в новый слой, который наследует от слоя модели Backend.
Дополнительная информация об этой теме можно найти в Эта статья Отказ
Frontend Model.
Поскольку бэкэнда является источником истины, она должна реализовать всю бизнес-логику, а интерфейс не должен реализовать. Итак, модель Frontend просто унаследована от бэкэндской модели, почти без дополнений.
Пользовательский интерфейс
Мы обычно внедряем модель Frontend и UI в два отдельных слоях. Но как я показал в Эта статья , это не обязательно.
Когда модель Frontend выполнена из классов, можно инкапсулировать простые виды как простые методы. Не беспокойтесь, если вы не видите, что я имею в виду прямо сейчас, это станет яснее в примере позже.
Поскольку модель Frontend в основном пуста (см. Выше), нормально реализовать пользовательский интерфейс непосредственно в него, поэтому нет слоя пользовательского интерфейса На по себе Отказ
Реализация UI в отдельном слое все еще необходима, когда мы хотим поддержать несколько платформ (E.g., веб-приложение и мобильное приложение). Но, поскольку это просто вопрос наследования слоя, который может прийти позже в дорожной карте развития.
Положить все вместе
Единая архитектура позволила нам унифицировать шесть физических слоев в один из одного логического слоя:
- В минимальной реализации доступом к данным инкапсулирован в бэкэндскую модель, и то же самое касается UI, который инкапсулирован в модель Frontend.
- Модель Frontend наследует от модели Backend.
- Слои API больше не требуются.
Опять же, вот как выглядит полученная реализация:
Это довольно зрелище, не так ли?
Связь
Чтобы реализовать единую архитектуру, все, что нам нужно, это перекрестное наследование, и я начал строить Связь чтобы достичь именно это.
Вы можете увидеть связь в качестве основы, если вы хотите, но я предпочитаю описать его как расширение языка, потому что все его функции лежат на самом низком возможном уровне – уровень языка программирования.
Итак, связь не блокирует вас в предопределенной структуре, а целая вселенная может быть создана сверху. Вы можете прочитать больше по этой теме в Эта статья Отказ
За сценой связи опирается на RPC механизм. Итак, поверхностно, это можно увидеть как что-то вроде CORBA , Java RMI или .СЕТЬ CWF Отказ
Но связь радикально отличается:
- Это не Распределенная объектная система Отказ Действительно, бэкэнд связи без гражданства, поэтому нет общих объектов между слоями.
- Он реализован на уровне языка (см. Выше).
- Его дизайн проста, и он обнародует минимальную API.
- Он не включает никакого кода котел, сгенерированный код, файлы конфигурации или артефакты.
- Он использует простой, но мощный протокол сериализации ( deepr ), который обеспечивает уникальные функции, такие как призыв, автоматический дозирование или частичное выполнение.
Связь начинает свое путешествие в JavaScript, но проблема, которую она занимается всемирной, и она может быть перенесена на любой объектно-ориентированный язык без слишком большого количества неприятностей.
Привет
Давайте проиллюстрируем, как обрабатывает связь, внедряя классический «счетчик» пример как одностраничное приложение.
Во-первых, нам нужен какой-то общий код между Frestend и Backend:
// shared.js
import {Model, field} from '@liaison/liaison';
export class Counter extends Model {
// The shared class defines a field to keep track of the counter's value
@field('number') value = 0;
}
Затем давайте построим бэкэнд, чтобы реализовать бизнес-логику:
// backend.js
import {Layer, expose} from '@liaison/liaison';
import {Counter as BaseCounter} from './shared';
class Counter extends BaseCounter {
// We expose the `value` field to the frontend
@expose({get: true, set: true}) value;
// And we expose the `increment()` method as well
@expose({call: true}) increment() {
this.value++;
}
}
// We register the backend class into an exported layer
export const backendLayer = new Layer({Counter});
Наконец, давайте построим интерфейс:
// frontend.js
import {Layer} from '@liaison/liaison';
import {Counter as BaseCounter} from './shared';
import {backendLayer} from './backend';
class Counter extends BaseCounter {
// For now, the frontend class is just inheriting the shared class
}
// We register the frontend class into a layer that inherits from the backend layer
const frontendLayer = new Layer({Counter}, {parent: backendLayer});
// Lastly, we can instantiate a counter
const counter = new frontendLayer.Counter();
// And play with it
await counter.increment();
console.log(counter.value); // => 1
В чем дело? Призывая Counter.ruement () Мы получили стоимость счетчика. Обратите внимание, что увеличение () Метод не реализован в классе Frontend, ни в общем классе. Это существует только на бэкере.
Итак, как это возможно, что мы могли бы назвать это из интерфейса? Это связано с тем, что класс Frontend зарегистрирован в слое, который наследует от бэкэндского слоя. Таким образом, когда метод отсутствует в классе Frontend, и метод с тем же именем выставлен в классе Backend, он автоматически вызывается.
С точки зрения интерфейса, операция прозрачна. Не нужно знать, что метод вызывается удаленно. Это просто работает.
Текущее состояние экземпляра (т.е., счетчик атрибуты) автоматически перевозится взад и вперед. Когда метод выполнен в бэкэнде, атрибуты, которые были изменены в интерфейсе, отправляются. И обратно, когда некоторые атрибуты меняются на бэкэнде, они отражаются в интерфейсе.
Обратите внимание, что в этом простом примере бэкэнда не совсем дистанционный. Как интерфейс, так и бэкэнда проходят в том же время выполнения JavaScript. Чтобы сделать бэкэнда действительно удаленной, мы можем легко разоблачить его через http. Увидеть Пример здесь Отказ
Как насчет передачи/возвращения значений в/из удаленно вызываемого метода? Можно пройти/возвращать все, что является сериализацией, включая экземпляры классов. Пока класс зарегистрирован с тем же именем как в Frontend, так и в Backend, его экземпляры могут автоматически перевозиться.
Как насчет переопределения метода через интерфейс и бэкэнд? Это не отличается от обычного JavaScript – мы можем использовать супер Отказ Например, мы можем переопределить увеличение () Способ для запуска дополнительного кода в контексте интерфейса:
// frontend.js
class Counter extends BaseCounter {
async increment() {
await super.increment(); // Backend's `increment()` method is invoked
console.log(this.value); // Additional code is executed in the frontend
}
}
Теперь давайте построим пользовательский интерфейс с Реагировать и инкапсулированный подход показан ранее:
// frontend.js
import React from 'react';
import {view} from '@liaison/react-integration';
class Counter extends BaseCounter {
// We use the `@view()` decorator to observe the model and re-render the view when needed
@view() View() {
return (
{this.value}
);
}
}
Наконец, отображать счетчик, все, что нам нужно, это:
Воила! Мы создали одностраничное приложение с двумя унифицированными слоями и инкапсулированным пользовательским интерфейсом.
Доказательство концепции
Чтобы экспериментировать с единой архитектурой, я построил Пример приложения Realworld с связи.
Я мог бы быть предвзятым, но результат выглядит довольно удивительно: простая реализация, сплоченность высокого кода, 100% Сухой и без клеевого кода.
С точки зрения количества кода, моя реализация значительно легче, чем любой другой, которую я исследовал. Проверьте Результаты здесь Отказ
Конечно, пример RealWorld – это небольшое приложение, но поскольку оно охватывает наиболее важные концепции, которые являются общими для всех приложений, я уверен, что единая архитектура может масштабироваться до более амбициозных приложений.
Заключение
Разделение опасений, свободных сцеплений, простоты, сплоченности и ловкости.
Кажется, мы все все, наконец.
Если вы опытный разработчик, я думаю, вы чувствуете себя немного скептически на данный момент, и это совершенно нормально. Трудно оставить годы установленных практик.
Если объектно-ориентированное программирование не ваша чашка чая, вы не захотите использовать связь, и это также совершенно нормально.
Но если вы в OOP, пожалуйста, оставьте маленькое окно открытым в своем уме, и в следующий раз вам придется создать приложение для полного стека, постарайтесь посмотреть, как он вписывается в единую архитектуру.
Связь Все еще на ранней стадии, но я активно работаю над этим, и я ожидаю выпустить первую бета-версию в начале 2020 года.
Если вы заинтересованы, пожалуйста, звезда Репозиторий и оставаться в курсе, следуя за Блог или подписаться на рассылка Отказ
Обсудите эту статью о Новости ChangeLog Отказ
Оригинал: “https://www.freecodecamp.org/news/full-stack-unified-architecture/”