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

Я сравниваю абстракцию в программных приложениях с популярными шаблонами, такими как Composable Architecture или MACH и им подобными. С помощью трех реальных сценариев я надеюсь продемонстрировать, как мое приложение стало более гибким и позволяет мне «быстро выходить из строя и быстро восстанавливаться», а также предотвращать накопление технического долга.

Примечание. Я ожидаю, что более опытные разработчики программного обеспечения будут знакомы с нижеследующим, так что это в основном предназначено для разработчиков программного обеспечения на ранних этапах их карьеры.

Сценарий 1 :: API

Команда корпоративного поиска объявила, что прекращает работу с текущим API поиска (даже не с другой версией). Это произошло из-за переноса поисковой системы с IDOL на пакетную поисковую систему.

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

А теперь представьте, сколько времени потребовалось всем этим командам-потребителям, чтобы узнать об этой новой поисковой платформе, ее API, а затем провести рефакторинг своей кодовой базы… Однако это не просто время, а как насчет того факта, что этот тип работы, безусловно, незапланированные и сбои в дорожной карте доставки команды-потребителя?

Какова альтернатива, спросите вы? Проще говоря… абстракция. Команда корпоративного поиска могла бы избежать раскрытия API платформы поисковой системы, предоставив своим потребителям уровень абстракции. Группа корпоративного поиска будет полностью контролировать схемы этого API. Это позволило бы им изменить поставщика поисковой системы, стоящего за этим уровнем абстракции, на свое усмотрение с минимальным воздействием на пользователей. Если поставщик снова изменится, может потребоваться максимум новая версия API.

Сценарий 2 :: Компоненты пользовательского интерфейса

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

Когда я впервые присоединился к организации, первая версия нашего веб-приложения была разработана с использованием внутренней библиотеки компонентов пользовательского интерфейса, которая соответствует руководству по стилю компании. К сожалению, эта библиотека содержала некоторые ошибки, была довольно негибкой (отсутствие поддержки более продвинутых реквизитов), и у команды не было необходимых ресурсов, чтобы по-настоящему доработать библиотеку. Мне нужно было доставить веб-приложение, причем быстро… и этого не могло случиться с библиотекой компонентов пользовательского интерфейса компании. Веб-приложение было основано на фреймворке Vue (это история для другого дня), и я не был слишком хорошо знаком ни с фреймворком, ни с какими-либо готовыми библиотеками компонентов. После некоторых исследований я остановился на Vuetify и Quasar, и в конце концов я решил, что Quasar будет лучшим выбором. Был ли я абсолютно уверен? Не совсем… Буду ли я когда-нибудь обязан вернуться к внутренней библиотеке пользовательского интерфейса? Возможно…

Так как же мне свести к минимуму усилия по возможной будущей доработке? Абстракция! Мы обернули все компоненты Quasar в наши собственные компоненты. Это в значительной степени дало нам нашу собственную абстрагированную библиотеку компонентов пользовательского интерфейса. В веб-приложении мы будем ссылаться только на наши собственные компоненты вместо компонентов Quasar. Так что, если в будущем мне когда-нибудь понадобится вернуться к внутренней библиотеке компонентов или появится что-то лучше, чем Quasar… тогда все, что потребует обновления, — это абстрактные компоненты. Давайте посмотрим на это на простом примере с компонентом Button:

Обёртка компонента Quasar q-btn:

<template>
  <q-btn
    :class="['button', btnStyle, classes]"
    :disabled="disabled"
    :icon="leadingIcon"
    :icon-right="trailingIcon"
    :label="label"
    :style="btnStyle"
    :loading="loading"
    @click="clicked"
  >
    <template #default>
      <slot />
    </template>
  </q-btn>
</template>

<script>
  export default {
    name: 'MyButton',
    props: {
      classes: {
        type: String,
      },
      btnStyle:
        default: 'primary',
        type: String,
      label: {
        type: String,
      },
      leadingIcon: {
        required: false,
        type: String,
      },
      trailingIcon: {
        required: false,
        type: String,
      },
    },
    methods: {
      clicked() {
        console.log('Button was clicked!');
      },
    },
  };
</script>

Использование абстрактного компонента:

<my-button
  class="actions"
  btnStyle="secondary"
  :label="action.label"
/>

Сценарий 3 :: Услуги

Теперь это возвращает меня к моим временам Java-разработчика и написанию объектно-ориентированного (ОО) кода. Хотя для этого сценария я использовал JavaScript, я спорил с самим собой, на какой шаблон Java это больше всего похоже. Его основная концепция — интерфейс-реализация, но есть несколько паттернов, таких как Мост, Фасад и т. д. Думаю, я остановился на Прокси-паттерне.

Передо мной стояла задача улучшить мониторинг моего веб-приложения с помощью Azure App Insights. Мой уровень API «интерфейс для бэкенда» подключается к более чем 20 другим конечным точкам API. Поэтому мой API хорош настолько, насколько хороши API, которые он потребляет. Когда возникают инциденты с моим веб-приложением, обычно это происходит из-за того, что мой API не может подключиться к одному из потребляющих API или потому, что владелец вышестоящего API решил внести критические изменения в схему. Это означает, что мне нужен был способ максимально эффективно устранять такие проблемы.

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

Чтобы решить эту проблему, мне пришлось реализовать перехватчики. Использование перехватчика позволило бы мне вручную перехватывать данные запросов и ответов, сопоставлять их с потоком Azure App Insights и отправлять их в Azure для регистрации. Для взаимодействия с более чем 20 вышестоящими API я реализовал библиотеку node-fetch, поскольку она практически идентична библиотеке fetch, используемой во внешнем веб-приложении.

К сожалению, node-fetch не позволяет перехватывать объекты запросов и ответов. Есть еще одна библиотека, которую я использовал, которая предоставляет низкоуровневый перехватчик HTTP Node, но я не хотел перехватывать каждый отдельный запрос/ответ, особенно те, которые выполняют аутентификацию OAuth с использованием сторонних библиотек.

В итоге я заменил node-fetch на axios из-за его возможностей перехватчика. На самом деле это было больше работы, чем я ожидал, потому что каждый сервис, выполняющий вызов REST, реализовывал библиотеку node-fetch в своем собственном файле. Поэтому, заменив node-fetch на axios, я воспользовался возможностью реализовать некоторую абстракцию и в итоге получил следующий шаблон:

// api.js

const axios = require('axios');

const client = axios.create({});
const defaultHeaders = {
  'content-type': 'application/json',
  Accept: 'application/json',
};

const get = async (url, headers, params) => {
  return client.get(url, {
    params,
    headers: {
      ...defaultHeaders,
      ...headers,
    },
  });
};
// azureClient.js

const api = require('./api');
const ApiError = require('./ApiError');

const AZURE_BASE_URL = process.env.AZ_BASE_URL;

const getToken = async () => {
  ...
};

const get = async (path, params) => {
  const accessToken = await getToken();
  
  try {
    const response = await api.get(
      `${AZURE_MANAGEMENT_BASE_URL}${path}`,
      { Authorization: `Bearer ${accessToken}` },
      params,
    );
  
    if (response && response.status === 200) return response.data;

  } catch (error) {
    const { status, statusMessage } = error.response;
    throw new ApiError({ status, statusMessage });
  }
};
// aksService.js

const azureClient = require('./azureClient');
const AzureError = require('./AzureError');

const subscriptionId = process.env.AZ_SUBSCRIPTION;

exports.getAKSVersions = async () => {
  const url = `/subscriptions/${subscriptionId}/providers/Microsoft.ContainerService/locations/eastus2/orchestrators`;
  const params = {
    'api-version': '2019-08-01',
    'resource-type': 'managedClusters',
  };
  try {
    return azureClient.get(url, params);
  } catch (error) {
    throw new AzureError({
      message: 'Error occured whilst fetching AKS Versions from Azure.',
      error.status,
      error.statusMessage,
    });
  };
};

Теперь в будущем, если мне когда-нибудь понадобится заменить axios на другую библиотеку, мне нужно будет только коснуться api.js, что должно вызвать минимальные нарушения остального кода.

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

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

Не стесняйтесь оставлять комментарии, если считаете сценарии информативными или у вас есть собственные примеры, которыми вы хотели бы поделиться!