Современный дизайн приложений .NET

В этой заметке мы рассмотрим несколько современных архитектур проектирования приложений, среди которых преобладает архитектура, основанная на событиях и сообщениях. После этого, в основном теоретического обсуждения, мы перейдем к практической реализации. Для этого мы рассмотрим два различных сервиса AWS. Первая из этих служб, Amazon Simple Notification Service (SNS), представляет собой управляемую службу обмена сообщениями, которая позволяет отделить издателей от подписчиков. Вторая служба — Amazon EventBridge, которая представляет собой бессерверную шину событий. По мере изучения каждой из них мы также рассмотрим включение этих служб в приложение .NET, чтобы вы могли увидеть, как это работает.

Современный дизайн приложений

Рост публичного облака и его способность быстро масштабировать вычислительные ресурсы вверх и вниз значительно упростили создание сложных систем. Давайте начнем с рассмотрения того, что делает для вас Microservice Extractor for .NET. Для тех, кто не знаком с этим инструментом, вы можете ознакомиться с руководством пользователя или посмотреть статью в блоге о его использовании. В основном, однако, инструмент анализирует ваш код и помогает определить, какие области кода можно выделить в отдельный микросервис. На рисунке 1 показан первоначальный дизайн, а затем дизайн после запуска экстрактора.

Рисунок 1. Дизайн до и после запуска Microservice Extractor

Почему это важно? Подумайте о вероятном использовании этой системы. Если вы подумаете о типичной системе электронной коммерции, вы увидите, что логика инвентаризации, логика, которая была извлечена, является очень используемым набором логики. Она необходима для работы со страницами каталога. Она необходима при работе с заказами или корзиной. Это означает, что данная логика может стать узким местом для всего приложения. Чтобы обойти это при первоначальном проектировании, необходимо развернуть дополнительные веб-приложения, чтобы облегчить нагрузку и минимизировать это узкое место.

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

Рисунок 2. Дизайн системы на основе микросервисов

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

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

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

Рассмотрим рисунок 3 и то, что потребовалось бы для создания этого нового мобильного приложения при использовании «старого» подхода. Скорее всего, бизнес-логика была бы продублирована, а это значит, что по мере развития каждого из приложений они, скорее всего, отдалились бы друг от друга. Теперь эта логика находится в одном месте, поэтому она всегда будет одинаковой для обоих приложений (за исключением логики, заложенной в пользовательский интерфейс приложения, который может развиваться по-разному — но кто этим занимается?)

Рисунок 3. Система на основе микросервисов, поддерживающая несколько приложений

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

Глубокое погружение в развязку

Не заглядывая в системы глубже, чем показано на рисунке 3, вы должны увидеть один аспект тесной связи, который мы не рассмотрели. «Исходная база данных». Да, эта общая база данных указывает на то, что между различными веб-сервисами все еще существует менее чем оптимальная связь. Вспомните, как мы использовали Extractor для извлечения службы «Инвентаризация», чтобы мы могли масштабировать ее независимо от обычного приложения. Мы не сделали то же самое со службой базы данных, к которой обращаются все эти веб-службы. Таким образом, у нас все еще есть эта проблема, только на уровне базы данных, а не на уровне бизнес-логики.

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

Рисунок 4. Разделение базы данных для поддержки разделенных сервисов

К сожалению, это не так просто. Подумайте о том, что происходит внутри каждой из этих различных служб; насколько полезны «Корзина» или «Заказ» без каких-либо знаний об «Инвентаре», который добавляется в корзину или продается? Конечно, этим сервисам не нужно знать все об «инвентаре», но им нужно либо взаимодействовать с сервисом «Инвентарь», либо обращаться непосредственно к базе данных для получения информации. Эти два варианта показаны на рисунке 5.

Рисунок 5. Совместное использование данных через общую базу данных или вызовы сервиса к сервису

Однако, как вы видите, мы только что добавили некоторую связь, поскольку при любом подходе служба «Заказ» и служба «Корзина» теперь в той или иной форме зависят от службы «Инвентаризация». Однако это может быть неизбежным, исходя из определенных бизнес-требований — тех требований, которые означают, что «Заказ» должен знать о «Инвентаре». Прежде чем мы слишком сильно напряжемся по поводу этой конструкции, давайте еще больше разделим это «необходимо знать», добавив дополнительное соображение о том, когда приложение должно знать об этих данных. Это поможет нам понять требуемую согласованность.

Сильная согласованность

Сильная согласованность означает, что все приложения и системы видят одни и те же данные в одно и то же время. Решения на рисунке 3 представляют этот подход, поскольку независимо от того, обращаетесь ли вы к базе данных напрямую или через веб-сервис, вы видите самый актуальный набор данных, и он доступен сразу после сохранения данных. Вполне могут быть требования, где это необходимо. Однако с такой же легкостью могут быть требования, когда небольшая задержка между получением информации службой «Инвентаризация» и службой «Корзина» может быть приемлемой.

Например, рассмотрим, как изменение доступности запасов (количества, доступного для продажи продукта) может повлиять на систему корзины иначе, чем на систему заказов. Корзина представляет товары, которые были выбраны как часть заказа, поэтому для нее важна доступность запасов — она должна знать, что товары доступны, прежде чем эти товары могут быть обработаны как заказ. Но когда она должна это знать? Здесь в игру вступают бизнес-требования. Если пользователь должен узнать об изменении сразу же, это, скорее всего, потребует определенной формы сильной согласованности. С другой стороны, если доступность запасов важна только в момент размещения заказа, то сильная согласованность не так необходима. Это означает, что в данном случае может потребоваться эвентуальная согласованность.

Эвентуальная согласованность

Как следует из названия, данные будут согласованы в различных службах в конечном итоге — не сразу. Эта разница может быть незначительной, как миллисекунды, а может составлять секунды или даже минуты, все зависит от потребностей бизнеса и дизайна системы. Чем меньше временные рамки, тем больше вероятность того, что вам понадобится сильная согласованность. Однако существует множество случаев, когда секунды и даже минуты вполне приемлемы. Например, заказ нуждается в информации о продукте, чтобы у него был контекст. Это может быть просто название продукта или более сложные отношения, такие как склады и места хранения продуктов. Но ключевым фактором является то, что изменения в этих данных не обязательно должны быть немедленно доступны системе заказов. Нужно ли системе заказов знать о новом продукте, добавленном в инвентарный список? Скорее всего, нет — поскольку крайне маловероятно, что этот новый продукт будет включен в заказ в течение миллисекунд после того, как он станет активным. Доступность в течение нескольких секунд должна быть просто отличной. На рисунке 6 показан график временного ряда различий между сильной и конечной согласованностью.

Рисунок 6. Временной ряд, показывающий разницу между сильной и конечной согласованностью

Что означает концепция конечной согласованности, когда мы смотрим на рисунок 3, показывающий, как эти три службы могут иметь некоторую связь? Это дает нам возможность смены парадигмы. До сих пор мы предполагали, что данные хранятся в одном источнике, независимо от того, хранятся ли все данные в большой базе данных или каждая служба имеет свою собственную базу данных — например, служба Inventory «владеет» базой данных Inventory, в которой хранится вся информация об инвентаризации. Таким образом, любая система, нуждающаяся в данных инвентаризации, должна будет каким-то образом пройти через эти службыбазы данных.

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

О нет! Только не дублирование данных!

Да, это означает, что некоторые данные могут быть сохранены в нескольких местах. И знаете что? Это нормально. Потому что это будут не все данные, а только те части данных, которые могут быть интересны другим системам. Это означает, что базы данных в системе могут выглядеть так, как показано на рисунке 7, где может быть дублирование сохраняемых данных.

Рисунок 7. Дублирование данных между базами данных

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

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

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

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

Обмен сообщениями

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

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

Несмотря на то, что сообщения имеют тенденцию быть целевыми, они обеспечивают развязку, поскольку нет требования, чтобы целевая система была доступна в момент отправки сообщения. Если целевая система не работает, то сообщение будет храниться до тех пор, пока система не восстановится и не начнет принимать сообщения. Любые пропущенные сообщения будут обрабатываться по принципу «первым пришел — первым ушел», и целевая система сможет самостоятельно доделать свою работу, не затрагивая систему-отправитель.

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

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

Главное, что следует учитывать при обмене сообщениями, — это то, что он ориентирован на гарантированную доставку и однократную обработку. Это дает представление о типах операций, которые лучше всего поддерживать с помощью обмена сообщениями. В качестве примера можно привести веб-приложение, отправляющее заказ. Подумайте о хаосе, если бы этот заказ был получен и обработан некоторыми службами, но не службой заказа. Вместо этого отправка должна быть сообщением, адресованным службе заказов. Конечно, во многих случаях мы обрабатываем это как HTTP-запрос (обратите внимание на сходство между сообщением и HTTP-запросом), но это не всегда лучший подход. Вместо этого наша система заказа отправляет сообщение, которое гарантированно доставляется к единственной цели.

События

События, с другой стороны, традиционно используются для представления «чего-то произошедшего» — действия, выполненного сервисом, которое может заинтересовать другие системы. События предназначены для тех случаев, когда вам нужно:

  • Масштабируемое потребление — несколько систем могут быть заинтересованы в содержимом одного события.
  • история — история «того, что произошло» может быть полезной. Как правило, база данных предоставляет текущее состояние информации. История событий дает представление о том, когда и что вызвало изменения в этих данных. Это может быть очень ценной информацией.
  • Неизменяемые данные — поскольку событие представляет собой «нечто, что уже произошло», данные, содержащиеся в событии, являются неизменяемыми — эти данные нельзя изменить. Это позволяет очень точно отслеживать изменения, включая возможность воссоздания изменений в базе данных.

События обычно предназначены для отправки системой, при этом систему не волнует, получат ли другие системы это событие или будут действовать в соответствии с ним. Отправитель запускает событие, а затем забывает о нем.

Если принять во внимание развязанную конструкцию, над которой мы работали ранее, становится быстро очевидным, что события — это лучший подход для предоставления измененных данных инвентаризации другим системам. В следующей статье мы перейдем непосредственно к Amazon Simple Notification Service (SNS) и поговорим о событиях в нашем приложении, используя SNS в качестве руководства.

Оцените статью
devanswers.ru
Добавить комментарий