- Что такое ООП?
- Назовите основные принципы ООП.
- Что такое «инкапсуляция»?
- Что такое «наследование»?
- Что такое «полиморфизм»?
- Что такое «абстракция»?
- Что представляет собой «обмен сообщениями»?
- Расскажите про основные понятия ООП: «класс», «объект», «интерфейс».
- В чем заключаются преимущества и недостатки объектно-ориентированного подхода в программировании?
- Что подразумевают в плане принципов ООП выражения «является» и «имеет»?
- В чем разница между композицией и агрегацией?
- Что такое статическое и динамическое связывание?
- Принцип SOLID?
- Что такое Монолит, Микросервис?
- Что выбрать Монолит или Микросервис?
- Взаимодействие между микросервисами
- OpenAPI
- Тестирование микросервисов
- Распределенная трассировка
Объектно-ориентированное программирование (ООП) — методология программирования, основанная на представлении программы в виде совокупности объектов, каждый из которых является экземпляром определенного класса, а классы образуют иерархию наследования.
- объектно-ориентированное программирование использует в качестве основных логических конструктивных элементов объекты, а не алгоритмы;
- каждый объект является экземпляром определенного класса
- классы образуют иерархии.
Программа считается объектно-ориентированной, только если выполнены все три указанных требования. В частности, программирование, не использующее наследование, называется не объектно-ориентированным, а программированием с помощью абстрактных типов данных.
Согласно парадигме ООП программа состоит из объектов, обменивающихся сообщениями. Объекты могут обладать состоянием, единственный способ изменить состояние объекта - послать ему сообщение, в ответ на которое, объект может изменить собственное состояние.
- Инкапсуляция - сокрытие реализации.
- Наследование - создание новой сущности на базе уже существующей.
- Полиморфизм - возможность иметь разные формы для одной и той же сущности.
- Абстракция - набор общих характеристик.
- Посылка сообщений - форма связи, взаимодействия между сущностями.
- Переиспользование- все что перечислено выше работает на повторное использование кода.
Это единственно верный порядок парадигм ООП, так как каждая последующая использует предыдущие.
Инкапсуляция – это свойство системы, позволяющее объединить данные и методы, работающие с ними, в классе и скрыть детали реализации от пользователя, открыв только то, что необходимо при последующем использовании.
Цель инкапсуляции — уйти от зависимости внешнего интерфейса класса (то, что могут использовать другие классы) от реализации. Чтобы малейшее изменение в классе не влекло за собой изменение внешнего поведения класса.
Наследование – это свойство системы, позволяющее описать новый класс на основе уже существующего с частично или полностью заимствующейся функциональностью.
Класс, от которого производится наследование, называется предком, базовым или родительским. Новый класс – потомком, наследником или производным классом.
Полиморфизм – это свойство системы использовать объекты с одинаковым интерфейсом без информации о типе и внутренней структуре объекта.
Преимуществом полиморфизма является то, что он помогает снижать сложность программ, разрешая использование одного и того же интерфейса для задания единого набора действий. Выбор же конкретного действия, в зависимости от ситуации, возлагается на компилятор языка программирования. Отсюда следует ключевая особенность полиморфизма - использование объекта производного класса, вместо объекта базового (потомки могут изменять родительское поведение, даже если обращение к ним будет производиться по ссылке родительского типа).
Полиморфизм бывает динамическим (переопределение) и статическим (перегрузка).
Полиморфная переменная, это переменная, которая может принимать значения разных типов, а полиморфная функция, это функция у которой хотя бы один аргумент является полиморфной переменной. Выделяют два вида полиморфных функций:
- ad hoc, функция ведет себя по разному для разных типов аргументов (например, функция
draw()
— рисует по разному фигуры разных типов); - параметрический, функция ведет себя одинаково для аргументов разных типов (например, функция
add()
— одинаково кладет в контейнер элементы разных типов).
Абстрагирование – это способ выделить набор общих характеристик объекта, исключая из рассмотрения частные и незначимые. Соответственно, абстракция – это набор всех таких характеристик.
Объекты взаимодействуют, посылая и получая сообщения. Сообщение — это запрос на выполнение действия, дополненный набором аргументов, которые могут понадобиться при выполнении действия. В ООП посылка сообщения (вызов метода) — это единственный путь передать управление объекту. Если объект должен «отвечать» на это сообщение, то у него должна иметься соответствующий данному сообщению метод. Так же объекты, используя свои методы, могут и сами посылать сообщения другим объектам. Обмен сообщениями реализуется с помощью динамических вызовов, что приводит к чрезвычайно позднему связыванию (extreme late binding).
Класс – это способ описания сущности, определяющий состояние и поведение, зависящее от этого состояния, а также правила для взаимодействия с данной сущностью (контракт).
С точки зрения программирования класс можно рассматривать как набор данных (полей, атрибутов, членов класса) и функций для работы с ними (методов).
С точки зрения структуры программы, класс является сложным типом данных.
Объект (экземпляр) – это отдельный представитель класса, имеющий конкретное состояние и поведение, полностью определяемое классом. Каждый объект имеет конкретные значения атрибутов и методы, работающие с этими значениями на основе правил, заданных в классе.
Интерфейс – это набор методов класса, доступных для использования. Интерфейсом класса будет являться набор всех его публичных методов в совокупности с набором публичных атрибутов. По сути, интерфейс специфицирует класс, чётко определяя все возможные действия над ним.
Преимущества:
- Объектная модель вполне естественна, поскольку в первую очередь ориентирована на человеческое восприятие мира, а не на компьютерную реализацию.
- Классы позволяют проводить конструирование из полезных компонентов, обладающих простыми инструментами, что позволяет абстрагироваться от деталей реализации.
- Данные и операции над ними образуют определенную сущность, и они не разносятся по всей программе, как нередко бывает в случае процедурного программирования, а описываются вместе. Локализация кода и данных улучшает наглядность и удобство сопровождения программного обеспечения.
- Инкапсуляция позволяет привнести свойство модульности, что облегчает распараллеливание выполнения задачи между несколькими исполнителями и обновление версий отдельных компонентов.
- Возможность создавать расширяемые системы.
- Использование полиморфизма оказывается полезным при:
- Обработке разнородных структур данных. Программы могут работать, не различая вида объектов, что существенно упрощает код. Новые виды могут быть добавлены в любой момент.
- Изменении поведения во время исполнения. На этапе исполнения один объект может быть заменен другим, что позволяет легко, без изменения кода, адаптировать алгоритм в зависимости от того, какой используется объект.
- Реализации работы с наследниками. Алгоритмы можно обобщить настолько, что они уже смогут работать более чем с одним видом объектов.
- Возможности описать независимые от приложения части предметной области в виде набора универсальных классов, или фреймворка, который в дальнейшем будет расширен за счет добавления частей, специфичных для конкретного приложения.
- Повторное использование кода:
- Сокращается время на разработку, которое может быть отдано другим задачам.
- Компоненты многоразового использования обычно содержат гораздо меньше ошибок, чем вновь разработанные, ведь они уже не раз подвергались проверке.
- Когда некий компонент используется сразу несколькими клиентами, улучшения, вносимые в его код, одновременно оказывают положительное влияние и на множество работающих с ним программ.
- Если программа опирается на стандартные компоненты, ее структура и пользовательский интерфейс становятся более унифицированными, что облегчает ее понимание и упрощает использование.
Недостатки:
- В сложных иерархиях классов поля и методы обычно наследуются с разных уровней. И не всегда легко определить, какие поля и методы фактически относятся к данному классу.
- Код для обработки сообщения иногда «размазан» по многим методам (иначе говоря, обработка сообщения требует не одного, а многих методов, которые могут быть описаны в разных классах).
- Документирование классов - задача более трудная, чем это было в случае процедур и модулей. Поскольку любой метод может быть переопределен, в документации должно говориться не только о том, что делает данный метод, но и о том, в каком контексте он вызывается.
- Неэффективность и неэкономное распределения памяти на этапе выполнения (по причине издержек на динамическое связывание и проверки типов на этапе выполнения).
- Излишняя универсальность. Часто содержится больше методов, чем это реально необходимо текущей программе. А поскольку лишние методы не могут быть удалены, они становятся мертвым грузом.
«является» подразумевает наследование. «имеет» подразумевает ассоциацию (агрегацию или композицию).
Ассоциация обозначает связь между объектами. Композиция и агрегация — частные случаи ассоциации «часть-целое».
Агрегация предполагает, что объекты связаны взаимоотношением «part-of» (часть). Композиция более строгий вариант агрегации. Дополнительно к требованию «part-of» накладывается условие, что экземпляр «части» может входить только в одно целое (или никуда не входить), в то время как в случае агрегации экземпляр «части» может входить в несколько целых.
Например, книга состоит из страниц и мы не можем вырвать страницу из книги и вложить в другую книгу. Страницы четко привязаны к конкретной книге, поэтому это композиция. В тоже время мы можем взять и перенести книгу из одной библиотеки в другую - это уже агрегация.
Связывание означает наличие связи между ссылкой и кодом. Например, переменная, на которую вы ссылаетесь, привязана к коду, в котором она определена. Аналогично, вызываемый метод привязан к месту в коде, где он определен. Присоединение вызова метода к телу метода. Если связывание проводится компилятором (компоновщиком) перед запуском программы, то оно называется статическим или ранним связыванием (early binding).
В свою очередь, позднее связывание (late binding) это связывание, проводимое непосредственно во время выполнения программы, в зависимости от типа объекта. Позднее связывание также называют динамическим (dynamic) или связыванием на стадии выполнения (runtime binding). В языках, реализующих позднее связывание, должен существовать механизм определения фактического типа объекта во время работы программы, для вызова подходящего метода. Иначе говоря, компилятор не знает тип объекта, но механизм вызова методов определяет его и вызывает соответствующее тело метода. Механизм позднего связывания зависит от конкретного языка, но нетрудно предположить, что для его реализации в объекты должна включаться какая-то дополнительная информация.
Итак, фундаментальное различие между статическим и динамическим связыванием в Java состоит в том, что первое происходит рано, во время компиляции на основе типа ссылочной переменной, а второе – позднее, во время выполнения, с использованием конкретных объектов.
Для всех методов Java используется механизм позднего (динамического) связывания, если только метод не был объявлен как static
или final
(приватные методы являются final
по умолчанию).
Ключевые различия между ранним и поздним связыванием в языке Java:
-
Статическое связывание происходит во время компиляции, а динамическое – во время выполнения.
-
Поскольку статическое связывание происходит на ранней стадии жизненного цикла программы, его называют ранним связыванием. Аналогично, динамическое связывание называют также поздним связыванием, поскольку оно происходит позже, во время работы программы.
-
Статическое связывание используется в языке Java для разрешения перегруженных методов, в то время как динамическое связывание используется в языке Java для разрешения переопределенных методов.
-
Аналогично, приватные, статические и терминальные методы разрешаются при помощи статического связывания, поскольку их нельзя переопределять, а все виртуальные методы разрешаются при помощи динамического связывания.
-
В случае статического связывания используются не конкретные объекты, а информация о типе, то есть для обнаружения нужного метода используется тип ссылочной переменной. С другой стороны, при динамическом связывании для нахождения нужного метода в Java используется конкретный объект.
Пять основных принципов дизайна классов:
- Single Responsibility Principle (Принцип единственной ответственности).
- Open Closed Principle (Принцип открытости/закрытости).
- Liskov’s Substitution Principle (Принцип подстановки Барбары Лисков).
- Interface Segregation Principle (Принцип разделения интерфейса).
- Dependency Inversion Principle (Принцип инверсии зависимостей).
Принцип единственной ответственности (SRP)
Никогда не должно быть больше одной причины изменить класс.
Класс должен быть ответственен лишь за что-то одно. Если класс отвечает за решение нескольких задач, его подсистемы, реализующие решение этих задач, оказываются связанными друг с другом. Изменения в одной такой подсистеме ведут к изменениям в другой.
Представьте себе модуль, который обрабатывает заказы. Если заказ верно сформирован, он сохраняет его в базу данных и высылает письмо для подтверждения заказа. Принцип единственной обязанности подразумевает, что три аспекта этой проблемы на самом деле — три разные обязанности. А значит, должны находиться в разных классах или модулях. Объединение нескольких сущностей, которые могут меняться в разное время и по разным причинам, считается плохим проектным решением. Гораздо лучше разделить модуль на три отдельных, каждый из которых будет выполнять одну единственную функцию.
Принцип открытости/закрытости (OCP)
Программные сущности (классы, модули, функции и т.п.) должны быть открыты для расширения, но закрыты для изменения.
Это означает, что должна быть возможность изменять внешнее поведение класса, не внося физические изменения в сам класс. Следуя этому принципу, классы разрабатываются так, чтобы для подстройки класса к конкретным условиям применения было достаточно расширить его и переопределить некоторые функции. Поэтому система должна быть гибкой, с возможностью работы в переменных условиях без изменения исходного кода. Используются интерфейсы
Принцип подстановки Барбары Лисков (LSP)
Объекты в программе можно заменить их наследниками без изменения свойств программы.
Это означает, что класс, разработанный путем расширения на основании базового класса, должен переопределять его методы так, чтобы не нарушалась функциональность с точки зрения клиента. То есть, если разработчик расширяет ваш класс и использует его в приложении, он не должен изменять ожидаемое поведение переопределенных методов. Если оказывается, что в коде проверяется тип класса, значит принцип подстановки нарушается.
Принцип разделения интерфейса (ISP)
Клиенты не должны быть вынуждены реализовывать методы, которые они не будут использовать.
Принцип разделения интерфейсов говорит о том, что слишком «толстые» интерфейсы необходимо разделять на более мелкие и специфические, чтобы клиенты мелких интерфейсов знали только о методах, необходимых в работе.
Рассмотрим пример. Разработчик Алекс создал интерфейс "отчет" и добавил два метода: generateExcel() и generatedPdf(). Теперь клиент А хочет использовать этот интерфейс, но он намерен использовать отчеты только в PDF-формате, а не в Excel. Он должен будет реализовать два метода, один из которых по большому счету не нужен и существует только благодаря Алексу — дизайнеру программного обеспечения. Клиент воспользуется либо другим интерфейсом, либо оставит поле для Excel пустым. Так в чем же решение? Оно состоит в разделении существующего интерфейса на два более мелких. Один — отчет в формате PDF, второй — отчет в формате Excel. Это даст пользователю возможность использовать только необходимый для него функционал.
Принцип инверсии зависимостей (DIP)
Зависимости внутри системы строятся на основе абстракций. Объектом зависимости должна быть абстракция, а не что-то конкретное. Модули верхнего уровня не зависят от модулей нижнего уровня. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций. Программное обеспечение нужно разрабатывать так, чтобы различные модули были автономными и соединялись друг с другом с помощью абстракции.
Классическое применение этого принципа — Spring framework. В рамках Spring framework все модули выполнены в виде отдельных компонентов, которые могут работать вместе. Они настолько автономны, что могут быть быть с такой же легкостью задействованы в других программных модулях помимо Spring framework. Это достигнуто за счет зависимости закрытых и открытых принципов. Все модули предоставляют доступ только к абстракции, которая может использоваться в другом модуле.
Монолитная архитектура - система с одним сервисом, который отвечает за работу со всей предметной областью бизнеса (приложения)
При начале работы над монолитным проектом Достоинства:
- Быстрая разработка
- Возможность внесения радикальных изменений (например изменить архитетуру, класс-дизайн, схему бд)
- Безболезненое тестирование
- Легкое развертывание
- Простое hardware масштабирование
С увеличением размера проекта и команды разработки появляются Минусы:
- Медленное внесение изменений Команды А и Б работают не пересекаясь. Выкатывается новая версия проекта с багами в коде команды Б. Откатывается на предыдущуюю версию весь код и команды Б и команды А.
- Уязвимая надежность - если падает часть кода, то не работает все приложение.
- Трудности в hardware масштабировании. Одна часть требует оперативу, другая ядра. Приходится апгрейдить "вертикально" - покупать больше оперативных планок и процессоры с большим кол-вом ядер для всех сервисов. А не по потребностям
- Дороговизна обновления технологического стека Например переход на новую версию Java. Чтобы проверить эффективность, нужно переписывать весь проект
Микросервисная архитектура — система, которая содержит больше одного обособленнго сервиса. Размер значения не имеет. Обособленный сервис — является независимой единицей развертывания, имеет своё хранилище, свою БД (?), отвечает за свою часть предметной области
Достоинства:
- Возможность масштабирования разработки
- Дешевизна эксперементов с новым стеком
- Инкрементальное обновление технолошического стека
- Повышенная надежность и отказоустойчивость
Минусы:
- Сложность проектирования архитектуры
- Долгое внесение радикальных изменений
- Нетривиальное тестирование
- Повышенные требования к Continuous Integration/Continuous Delivery (деплою)
Непрерывная интеграция (CI) — процесс автоматического принятия изменений. Первичный процесс обновления ПО, в рамках которого все изменения на уровне кода вносятся в единый центральный репозиторий. Такое внесение принято называть слиянием. После каждого слияния (которое проходит по несколько раз в день) в изменяемой системе происходит автоматическая сборка (часто приложение упаковывается в Docker) и тестирование (проверка конкретных модулей кода, UI, производительности, надёжности API). Таким образом разработчики страхуются от слишком поздних обнаружений проблем в обновлениях.
- Возможность локальной сборки проекта
- На каждый коммит в кдаленный репо запускаем сборки проекта в системе (Jenkins, TravisCI)
- Сломанная сборка запрещает слияние
Непрерывная доставка (CD) — CI + CD. Автоматизированый процесс доставки изменений до продакшена. Теперь новая версия не только создаётся и тестируется при каждом изменении кода, регистрируемом в репозитории, но и может быть оперативно запущена по одному нажатию кнопки развёртывания. Однако запуск развёртывания всё ещё происходит вручную — ту самую кнопку всё же надо кому-то нажать. Этот метод позволяет выпускать изменения небольшими партиями, которые легко изменить или устранить в случае необходимости.
- Поддерживаем артефакты стабильными
- Развертываем по кнопке
Сложности проектирования Микросервисов
- Сложно разделить предметную область на части Не стоит делить монолит на микросервисы по функциональным возможностям. Это приведет к распределенному монолиту - архитектура, включающая недостатки и того и другого. Монолит нужно разбивать по пренадлежности к какой-то области бизнеса. Отличить одно от другого довольно сложно. Т.е. по принципу единой ответственности.
Соответствие принципам Low coupling, high cohesion способствует быстрому внесению изменений и легкому тетсированию.
Сильная связанность high cohesion - части системы, которые изменяются вместе, должны находиться ближе друг к другу
Слабая связанность low coupling - части системы, которые изменяются параллельно, должны иметь как можно меньше зависимостей друг на друга
И внутри сервиса и между сервисами
- Задержки при межсервисном взаимодействии влияют на производительность + сеть ненадежна
- Межсервисное взаимодействие влияет на доступность
- Сложно добиться согласованности данных
Микро если:
- Предметная область хорошо делится на части
- Много направлений, которые нужно развивать параллельно
- "Особенности" монолита не устраивают бизнес
Монолит если:
- Нужно быстро сделать прототип
- Вашу задачу можно решить одним сервисом
- Непонятно, как разделить предметную область
- Низкие нагрузки, мало разработчиков, нет ресурсов на поддержание микросервисов
https://microservices.io/ https://martinfowler.com/ "microservices patterns" chris richardson "clean architecture" robert martin
Межсервисное взаимодействие = сетевое взаимодействие, т.е. система управляется сообщениями
- Производительность = задержки и пропускная способность
- Доступность и отказоустойчивость
- Архитектура
Транспорт
-
стандарт JSON внутри HTTP HTTP - простой, множество инструментов, человекочитаемость. Из-за текстового формата большие объемы и нужно оптимизировать
-
бинарные протоколы: gRPC, Thrift, Avro и т.д. Выше производительность, оптимизированно представление данных, можно определить схему сообщения из коробки, но меньше инструментов и не читаемы человеком
Что выбрать?
- Начинать с HTTP
- Бинарные не бесплатные
- Большинство проблем не из-за протокола
- Для публичных API всегда HTTP либо несколько реализаций одна из которых HTTP
API Application Programming Interface - интерфейс, определяющий способ и схему взаимодействия с сервисом
- Remote Procedure Call (RPC) Только POST метод. Все параметры в теле запроса. Любой статус !=200, считается ошибкой Выбирают: Простая реализация, Внутренние API, HTTP только как транспорт
- Representational State Transfer (REST) Архитектурный стиль, в основе которого лежат ресурсы и их идентификация, изменение их состояния через представление. REST != HTTP Выбирают: Своего рода стандарт, Внутренние API, Публичные API, HTTP как протокол
- GraphQL Язык запросов для API. Например можно задать конкретный набор полей, которые хотим получить в ответе на запрос Выбирают: Типо-безопасность, API для Web и мобильных клиентов, тут свой инструментарий
Проектирвоание
- Клиент-сервер
- Stateless - сервис не должен хранить состояние в памяти, это важно для масштабирования
- Кеширование
Типы сообщений
-
Команды Императивное управление (звучит как "сделать что-то"), вызывают сильную связанность, зато понятны - названия обычно отражают логику
-
События Декларативное управление (звучит как "что-то произошло"), снижают связанность, менее понятны - не знаем что скрыто в notifyOrderCreated
Виды взаимодействия
- Синхронные - отправляем запрос и блокируемся, ожидая результата. Просто и понятно, быстрые операции и низкие задержки (ок), низкая пропускная способность (не ок), вызывает низкую доступность (не ок)
- Ассинхронные - не блокируемся ожидая ответ Сложнее в реализации, выше задержки (не ок), выше пропускная способность (ок), повышает доступность (ок)
Short polling - вызываем операцию и получаем ссылку на ресурс с результатом операции. Переодически опрашиваем этот ресурс на предмет текущего статуса операции
Пользователь должен ждать как можно меньше, и нужно выполнять как можно меньше запросов на опрос
Стратегии опроса:
- Фиксированное время (раз в секунду) - просто, но время запроса может чуть отличаться, что приведет к лишним запросам
- Прогрессия (геометрическая/алгебраическая) - сокращает кол-во запросов, но возможен вариант когда придется долго ждать
- На основе статистического распределения - например, знаем, что запрос выполняется 50% за 1.5с, 90% за 3с и 99% за 5с, мы можем распределить запросы во времени
Callbacks - вызываем операцию и получаем идентификатор операции. Сервис, выполняющий операцию, отправляет уведомление вызвавшему сервису о результате выплонения операции
Способы взаимодействия
- Прямой обмен А->Б - просто, легкая эксплуатация, но сильная связанность сервисов. Хорошо для Команд
- Обмен через брокеры сообщений А->Брокер->Б - сложнее, позволяет снизить связанность сервисов. Хорошо для Событий На основе callback: RabbitMQ
Микросервисы могут работать одновеременно с разными диалектами БД. При этом есть сильная согласованность, когда изменения сразу видны всем, с момента их применения и конечная согласованность когда изменения будут видно, но не сразу, а доставляются асинхронно
Сильная согласованность - всегда актуальные данные, но высокие задержки при получении
В таких случаях используются Распределенные транзакции. Но это сложно, дорого и есть не везде. Можно эмулировать распределенную транзакцию с помощью вложенных локальных транзакций
При этом возврат ошибки не гарантирует, что запись не была вставленна в БД. А откат первой транзакции не приводит к откату второй.
Saga паттерн: Для решения нужно разбивать одну большую распределенную транзакцию на последовательность локальных транзакций и предоставить возможность отката всех локальных транзакций.
Особенности: нужно уметь восстанавливаться после сбоев любого участка и повторять сагу с определенного шага + отличать финальные ошибки от нефинальных Хореография
- Нет единой точки отказа
- Легко менять кусочек процесса
- Сложно понять процесс целиком и отслеживать ошибки
- Есть единая точка отказа
- Сложнее вносить изменения в места
- Легко понять процесс и отслеживать ошибки
Конечная согласованность - могут быть неактуальные данные, зато низкие задержки
Взаимоисключения
Иногда требуется выполнять какой-то процесс эксклюзивно. Например используя распределенный консенсус - алгоритмы, которые сложно и медленно работают. etcd, Consul, Zookeeper. Зато отказоустойчивы
Альтернатива - red lock, когда БД используется в качестве хранилища блокировок. Блокировка не захватывается, а арендуется на время. Чтобы в случае блокировки и отказа системы блокировка не осталась навсегда Добавляем версию блокировки для контроля ее корректности
OpenAPI - спецификация для описания REST API.
https://habr.com/ru/post/541592/
Swagger - это фреймворк для спецификации RESTful API, цель которого - поддерживать документацию в актуальном виде. Его прелесть заключается в том, что он дает возможность не только интерактивно просматривать спецификацию, но и отправлять запросы – так называемый Swagger UI.
- Code first - сначала пишем код, потом генерируем спецификацию
- Schema first - сперва спецификация, затем генерируем по ней код
- Публикуем спецификацию (отделный артефак или Swagger)
- Строго следовать этой спецификации
Документирование БД Database Schema as a Code - текущая схема должна быть представлена последовательностью патчей, хранимых как код
- Возможность восстановить БД на любом окружении
- Аудит, контроль, валидация
- Flyway, Liquibase
- Компонентные для тестирования логики приложения
- Модульные для тестирования вычислений
- Компонентных должно быть больше, чем модульных, т.к. изменяемая среда, много зависимочти между классами
Тесты должны быть независимыми и уметь работать параллельно. Тесты не должны очищать БД или буффер брокера сообщений перед/после своего запуска/завершения
Для тестирования API лучше делать реальные HTTP вызовы, валидировать ответ согласно схеме (JSON). RESTAssured как пример инструмента.
При тестирование взаимодействия с БД на каждый запуск тестов локально поднимается БД. Embedded или TestContainers
При тестировании взаимодействия микросервисов на каждый запуск тестов локально HTTP-сервер, создаем моки операций сервисов, выполняем, моки валидируем. WireMock, MockServer
При тестировании аинхронных процессов стараемся приблизить тестовый сценарий к реальному, запускаем операцию и ждем завершения выполнения. Awaitility
Для автоматизации развиваем Continuous Integration/Continuous Delivery - помогает быстрее развивать сервисы и доставлять их на production. Но дорого.
При межсервисном взаимодействии сложно отследить весь путь запроса по системе
- К каждому внешнему запрсос прикрепляем "идентификатор операции"
- Пробрасываем его по всей системе
- Добавляем его в логовые сообщения для отслеживания. Elastic APM, OpenTracing
Для HTTP: Прикрепляем HTTP-заголовок с этим идентификатором - Используем его при обработке запроса - Поллинг делаем с тем же индентификаторо - Обраный вызов с ним же - В брокер сообщений также отправляем этот индентификатор
Для Саги: Исходный запрос теряется при обработке асинхронных процессов - В задачу на продолжение саги кладем исходный идентификатор операции (в БД) - При возобновлении саги используем этот идентификатор
Визуализация трасс помогает построить схему взаимодействия между сервисами. Можно это делать атоматически и динамически. Zipkin, Jaeger