Skip to content

Пример микросервиса аутентификации, покрытого тестами и реализованного с применением идей DDD, CQRS, SOLID, GRASP, Clean Architecture, Design Patterns.

License

Notifications You must be signed in to change notification settings

waltan-dev/JoBoard.AuthService

Repository files navigation

Tests Docker Image CI

*еще в разработке

Пример микросервиса аутентификации, покрытого тестами и реализованного с применением идей DDD, CQRS, SOLID, GRASP, Clean Architecture, Design Patterns.

Требования
  1. .NET SDK 8.0
  2. Docker
  3. GNU Make (необязательно)
  4. EF Core tools (необязательно)
Внешние зависимости
  • PostgreSQL
  • Redis
Разработка

В решении есть makefile с командами (запускать из корневой директории), которые упрощают процесс разработки, например, команды для работы с миграциями:

  • Сгенерировать новую миграцию: make add-migration {Name}
  • Удалить последнюю миграцию: make remove-migration
  • Применить миграции к dev db: make migrate-dev-db

Функционал API:

Аутентификация:

  • Вход/Получение токенов по email и паролю
  • Вход/Получение токенов с помощью google аккаунта
  • Регистрация нового пользователя по email и паролю
  • Регистрация нового пользователя с помощью google аккаунта
  • Сброс пароля (запрос + подтверждение)
  • Refresh token
  • Выход/инвалидация refresh token

Управление аккаунтом:

  • Подтверждение email (запрос + подтверждение)
  • Изменить email (запрос + подтверждение)
  • Изменить пароль
  • Изменить роль
  • Привязать/отвязать google аккаунт
  • Деактивировать аккаунт (запрос + подтверждение)

Структура решения:

Структура решения выполнена в стиле Clean Architecture и разбита на уровни:

Domain:

Доменный слой реализован изолированным и с подходом DDD. Вся бизнес-логики содержится в доменном слое, а именно в агрегатах, сущностях, объектах-значениях и т.д. Используется т.н. подход Богатой модели (Rich model), а также паттерн Information expert из GRASP - объекты, которые содержат данные сами занимаются обработкой и валидацией этих данных.

В доменной модели этого микросервиса содержится один агрегат User. Он включает в себя сущность ExternalAccount и объекты значения: UserId, Email, FullName, Password, ConfirmToken. Агрегат User создаёт множество различных событий, связанных с действиями юзера, например UserChangedEmail, UserRegistered, UsedDeactivated и т.п. Кроме этого, агрегат User включает в себя множество бизнес-правил, которые реализованы в виде отдельных классов: UserEmailMustBeUnique, PasswordMustBeStrong и т.п.

Application:

  • В слое присутствует чёткое разделение на команды и запросы (CQRS). Внутри команд используются шаблоны DDD чтобы повысить качество кода, который изменяет состояние системы. А внутри запросов наоборот - шаблоны DDD не используются с целью повышения производительности за счёт использования microORM, хранимых процедур, представлений и т.д. для запросов.
  • Слой не привязан к конкретному способу аутентификации - можно подключать любые (cookies, JWT и т.д.) и использовать слой совместно с различными фреймворками.

Infrastructure:

Слой разбит на 3 проекта: Data, JWT, Auth.

  • В проекте Data содержится реализация репозиториев и паттерна UnitOfWork с помощью EF.
  • В проекте JWT содержится реализация компонентов: JwtGenerator, JwtIdentityService, JwtSignInManager, RedisRefreshTokenRepository.
  • В проекте Auth содержится реализация компонентов: PasswordHasher, PasswordValidator, SecureTokenizer, GoogleAuthProvider.

Tests:

В решении содержатся функциональные, интеграционные и юнит тесты (всего 200+ тестов). Все тесты реализованы на выполнение в параллельном режиме.

  • В интеграционных и функциональных тестах используются TestContainers для PostgreSQL и Redis, поэтому требуется Docker.

JoBoard.AuthService.FunctionalTests

Функциональные тесты проверяют работу приложения по функциональным требованиям и как приложение работает с точки зрения конечного пользователя/клиента. Такие тесты имитируют действия/запросы пользователя/клиента - регистрация, вход и т.д. Такие тесты называются функциональными, потому что они проверяют, что приложение корректно выполняет все функции, которые ожидаются от него. В проекте содержатся функциональные тесты, которые покрывают логику и публичный контракт API endpoints.

JoBoard.AuthService.IntegrationTests

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

SOLID

В решении в большинстве случаев соблюдаются принципы SOLID:

  1. Single Responsibility Principle. Все обработчики (handlers) команд и запросов в решении соблюдают принцип SRP. Например, класс RegisterByEmailAndPasswordCommandHandler занимается только регистрацией нового юзера и сохранением его в БД, но не отправкой почты или еще чем-либо. Отправка письма, связанного с регистрацией, происходит в другом обработчике UserRegisteredDomainEventHandler. Таким образом класс имеет лишь одну причину для изменения.

  2. Open-Closed Principle. Например, метод UserPassword.Create в качестве параметров принимает абстракции IPasswordStrengthValidator, IPasswordHasher и может менять поведение в зависимости от переданных реализаций. Таким образом класс открыт для расширения и закрыт для модификации. Т.е. можно изменять функционал за счёт добавления новых реализаций для абстракций без модификации существующего класса UserPassword.

  3. Liskov Substitution Principle. В решении никакие подклассы не изменяют и не замещают контракт базового класса. Например, класс EfUserRepository никак не изменяет контракт своего родителя EfBaseRepository.

  4. Interface Segregation Principle. Например, метод IsEmailUnique для проверки уникальности email юзера с помощью запроса к БД используется только в классе UserEmailMustBeUniqueRule. Если расположить этот метод в интерфейсе IUserRepository, то это будет считаться нарушением принципа ISP, т.к. клиенты, использующие IUserRepository будут зависеть от метода IsEmailUnique, который им не нужен. А также класс UserEmailMustBeUniqueRule будет зависеть от всех остальных методов репозитория, которые ему не нужны. Поэтому метод IsEmailUnique вынесен в отдельный интерфейс IUserEmailUniquenessChecker. Таким образом, класс UserEmailMustBeUniqueRule зависит только от метода IsEmailUnique, а все остальные классы, использующие IUserRepository не зависят от этого метода.

  5. Dependency Inversion Principle. В решении используется подход Clean Architecture и принцип DIP для организации направленности зависимостей между модулями. Так, модули верхнего уровня Domain и Application являются ядром приложения и содержат лишь интерфейсы/контракты компонентов, которые реализуются модулями нижнего уровня Infrastructure и API. При этом модули верхнего уровня и их контракты никак не зависят от модулей нижнего уровня - наоборот - только нижние зависят от верхних. Таким образом модули верхнего уровня не зависят от конкретной инфраструктуры и легко покрываются тестами.

Design Patterns

В решении используются некоторые популярные паттерны проектирования:

  1. Mediator + Facade Patterns. В решении используется подход CQRS в связке с паттерном Mediator. Это позволяет контроллерам и другим компонентам не зависеть от конкретных обработчиков. Кроме этого, контроллеры выполнены в "тонком" стиле и занимаются только делегированием работы медиатору, поэтому такие тонкие контроллеры являются примером паттерна Facade.

  2. Strategy Pattern. Например, метод UserPassword.Create принимает абстрактные стратегии IPasswordStrengthValidator, IPasswordHasher и может менять поведение в зависимости от переданных реализаций стратегий. Т.е. можно использовать разные стратегии валидации и хеширования паролей для различных ситуаций.

  3. Builder Pattern. В проектах тестов содержится класс UserBuilder, который упрощает создание объектов User в различных сценариях тестирования.

  4. Static Factory Method Pattern. В классе User есть статические фабричные методы RegisterByEmailAndPassword и RegisterByGoogleAccount, которые упрощают создание новых экземпляров класса User и являются единственным способом сделать это.

  5. Repository + UnitOfWork Patterns. В проекте Infrastructure.Data содержатся реализации паттернов Repository и UnitOfWork.

About

Пример микросервиса аутентификации, покрытого тестами и реализованного с применением идей DDD, CQRS, SOLID, GRASP, Clean Architecture, Design Patterns.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages