Подтверждение контракта |

два суслика пожимают друг другу руки по контракту

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

Давайте начнем с форм и форм этих проблем, а затем перейдем к различным системам, в которых они появляются.

Проблемы на основе схемы

Большинство проблем с контрактами — это просто нарушения схемы из-за использования языка без схемы, такого как JSON.

  • Несоответствие типов данных: для того же поля тип изменился с целого на число с плавающей запятой или строку на целое число и т. д.,…
  • Отсутствуют обязательные поля: клиент ожидает, что поле будет установлено, но новое изменение кода удалило поле
  • Свободные правила декодирования: некоторые языки json unmarshalling будет легко декодировать поле order_id, подобное этому {"order_id": "1"} в целочисленное поле, но если серверная сторона вдруг решит изменить его на uuid, клиенты внезапно начнут ломаться
  • Целочисленный размер: декодирование идентификатора пользователя в int32 может работать какое-то время, но если клиент не принимает больший целочисленный размер вместе с сервером или если служба не сообщает своим клиентам об изменении размера идентификатора, то вас ждет сюрприз
  • Переименование поля: в то время как некоторые тесты на стороне службы могут пройти с переименованием поля, но это изменение является несовместимым с предыдущими версиями.

Проблемы с контрактом данных:

Помимо схемы, контрактные тесты обычно зависят от данных, но даже здесь есть общие наборы шаблонов.

Значение по умолчанию

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

type BatchUpdateResponse struct {
  Success bool `json:"success"`
  FailedItems int `json:"total_items"`
  FailureReason string `json:"failure_reason"`
}

В приведенном выше примере поле «успех» указывает, был ли запрос успешным, и если это сбой, то будут установлены FailedItems и FailureReason. Из-за отсутствия тестирования контрактов или проверки контрактов во время выполнения до тех пор, пока не начнется значительный сбой производства, никто не понимает, что они не могут точно отладить, почему клиенты видят сбой, поскольку они не могут видеть причину сбоя. Наихудший сценарий — это когда запрос выполнен успешно, но поле «успех» не установлено из-за структуры кода, которая предварительно возвращалась, но поле успеха установлено только в конце, а не в начале, в этом случае мы в конечном итоге ломаем голову, почему запрос не удался, не указав причину отказа.

Неверные данные:

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

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

Проблемы проверки контрактов в системах

В API часто рекомендуется правильная настройка контрактов, но HTTP API — не единственные системы, которые сталкиваются с перебоями в работе, которые можно предотвратить с помощью правильных контрактов.

Системы, управляемые событиями

По мере того, как вы отделяете системы/микросервисы для более быстрых выпусков, мы движемся к микросервисам, управляемым событиями, где сервис публикует события, а другие сервисы слушают и обрабатывают. Kafka — самый популярный бэкэнд для управления рабочими процессами, управляемыми событиями; предположим, что производитель нарушает контракт или схему. В этом случае потеря события является наилучшим сценарием, а в случае ошибки декодирования у вас есть 2 варианта: игнорировать событие и зафиксировать смещение, которое оно обрабатывает. Это наилучший сценарий, когда событие теряется, но, допустим, смещение не фиксируется. Тем не менее, поток PHP падает или приложение вообще падает; в таких случаях вы повторно обрабатываете один и тот же недопустимый элемент и полностью прекращаете обработку других элементов. Пока человек не вмешается и явно не удалит или не будет отправлено новое изменение кода, эта ядовитая пилюля заблокирует обработку этой темы кафки. Настройка контракты времени выполнения, управляемые потребителем И тестирование схемы на стороне производителя — лучшее решение этой проблемы. А еще лучше использовать что-то вроде protobuf с необходимыми линтерами обратной совместимости, а не JSON.

Системы конфигурации

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

Те же принципы, что и для кода, необходимо применять и к конфигурации, когда речь идет о схеме и контракте. Это означает, что на уровне инфраструктуры конфигурация должна быть протестирована на соответствие схеме, предоставленной кодом, прежде чем будет развернуто изменение конфигурации. И то же самое справедливо для данных. Часто серверная часть включается с помощью параметра enable: true, но необходимая конфигурация, необходимая для включения этой серверной части, не тестируется. Большинство конфигурационных библиотек работают с конфигурацией на языке динамического типа, таком как «гадюка«; лучший подход к этой проблеме — использовать что-то вроде «envconfig”, где конфигурацию необходимо декодировать в строго типизированную структуру. Помимо этих данных, можно добавить проверку, чтобы убедиться, что контракт конфигурации соответствует схеме.

Кэширующие системы

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

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

// псевдокод func (c *Cache) Get(key string, value interface{}) (cacheMiss bool) { bts, err := redis.Get() if err != nil && errors.Is(err, redis.NilErr ) { вернуть true } err = json.Unmarshal(bts, data) if err != nil [
    return true
  }
  return false
}

func getOrderData() *Order {
  data := Order{}
  miss := cache.Get(fmt.Sprintf("order_data_%d", orderID))
  if !miss {
    return &data
  }
  data = database.Get(orderID)
  cache.Set(fmt.Sprintf("order_data_%d", orderID), data)
  return data
}

In the above code, the main issue is that upon decoding or redis network error, it started treating it as a cache miss and falling back to the database. This not only increases the load on the master but also starts setting the new corrupted cache inside the cache. It is better to treat contract failures in the cache as total failures than treat them as cache misses. You could still implement some budgeting for redis errors to be treated as misses without overloading the database but treating contract errors as cache misses could leave you with a corrupt cache in your redis

Feature Store:

Most storage systems are built on top of some schema & contract; because of this very reason that corrupt data is very painful to deal with. but for any high-scale company or with a high number of microservices, providing a good feature store is essential for powering ML, business rule engine, personalisation or features that are only available for few cohorts. Because this is a generic feature store, indexing is driven by ingesting data generated by an analytics system that crunches the data and sends it off for indexing inside the feature store. And a separate system reads this data. Because of this intermediate layer, the reader of this data can’t dictate the schema or contract validation before ingesting the data inside the database, as this feature store is centralised data storage.

Like a corrupt cache, this is a painful outage to handle if bad data is ingested; your users suddenly lose their personalised view of your site and start seeing like any normal user. Until all the data is reindexed with the correct schema, all the personalisation would be lost. The worst case is if this data starts throwing panic or translates to a critical error at the reader level.

writes

[data analytics job] ⇒ [feature store]

читает

[services] ⇒ [feature store]

Миграция с монолита на микросервис:

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

Системы планирования:

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

Решение:

Хорошей отправной точкой является использование строго типизированной схемы, такой как protobuf, для всех этих систем, то есть не только для grpc, но и для кэширования, организации очередей, систем, управляемых событиями, и хранилищ функций. Следующим шагом будет настройка базового статического анализа, например проверки обратной совместимости для всех этих систем с использованием protobuf. И, наконец, настройка статической проверки контрактов поможет обнаружить некоторые проблемы на самом уровне PR; для более сложных сценариев проверка контракта во время выполнения может помочь обнаружить проблемы на этапе автоматизированных интеграционных тестов до начала производства или на канареечном этапе.

Заключение:

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

Оригинал:

Похожие записи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *