1. Архитектура систем и паттерны проектирования
Архитектура систем и паттерны проектирования
Представьте, что вы строите одноэтажный дачный домик. Вам не нужен детальный чертеж фундамента с расчетом нагрузок — достаточно здравого смысла и базовых навыков. Но если вы решите возвести небоскреб, отсутствие архитектурного плана приведет к катастрофе еще на этапе заливки бетона. В разработке программного обеспечения переход от Junior к Middle — это как раз переход от «домиков» к «небоскребам». Разница между работающим кодом и качественной системой заключается в том, насколько легко этот код можно изменять, масштабировать и поддерживать через год после запуска.
Фундамент чистого кода: Принципы SOLID
Когда мы говорим об архитектуре, мы начинаем с атомарного уровня — классов и их взаимодействий. Большинство разработчиков слышали о SOLID, но на уровне Middle важно понимать не расшифровку аббревиатур, а те проблемы, которые эти принципы решают. Если ваш класс UserService одновременно проверяет валидность email, записывает данные в базу и отправляет поздравительную открытку — вы нарушили Single Responsibility Principle (SRP). В будущем любое изменение формата email заставит вас тестировать логику отправки открыток, что порождает хрупкость системы.
Особое внимание стоит уделить Dependency Inversion Principle (DIP). Начинающие разработчики часто создают объекты внутри других объектов: private final MySQLRepository repo = new MySQLRepository();. Это намертво привязывает бизнес-логику к конкретной реализации базы данных. Профессиональный подход требует зависимости от абстракции (интерфейса). Представьте, что вы проектируете систему оплаты. Ваша логика должна зависеть от интерфейса PaymentGateway, а не от конкретного StripeService. Это позволяет подменить Stripe на PayPal в конфигурации, не меняя ни строчки кода в методе оформления заказа.
> «Архитектура — это искусство принимать решения, которые позволяют откладывать другие решения как можно дольше». > > Роберт Мартин, "Чистая архитектура"
Слоистая архитектура и инверсия управления
Переходя на уровень выше, мы сталкиваемся с организацией всего приложения. Самый распространенный подход в Java-мире (Spring Framework) — это Layered Architecture (Многослойная архитектура). Обычно она включает в себя четыре уровня:
Главное правило здесь — строгая направленность зависимостей: верхние слои могут зависеть от нижних, но не наоборот. Если ваш слой доступа к данным знает о существовании HTTP-контроллера, у вас «протекла» абстракция. В сложных проектах Middle-разработчики часто переходят к Hexagonal Architecture (Порты и адаптеры). В ней бизнес-логика находится в центре и вообще не зависит от внешнего мира. Она определяет «порты» (интерфейсы), а внешние системы (БД, UI, сторонние API) подключаются через «адаптеры». Это делает систему неуязвимой к смене технологий.
Паттерны проектирования: Инструментарий Middle-разработчика
Паттерны — это типовые решения часто встречающихся задач. На уровне Middle недостаточно знать «Одиночку» (Singleton) или «Фабрику» (Factory). Важно понимать поведенческие и структурные паттерны, которые помогают управлять сложностью.
| Паттерн | Проблема | Решение | | :--- | :--- | :--- | | Strategy (Стратегия) | Нужно менять алгоритм во время выполнения программы (например, разные способы расчета скидки). | Вынос алгоритмов в отдельные классы с общим интерфейсом. | | Observer (Наблюдатель) | Одно событие должно вызывать действия в разных несвязанных модулях. | Подписка объектов на изменения состояния другого объекта. | | Decorator (Декоратор) | Нужно добавить функциональность объекту без изменения его кода и без наследования. | Обертка объекта в другой объект того же типа. | | Adapter (Адаптер) | Интерфейс сторонней библиотеки не совпадает с тем, который ожидает ваш код. | Создание промежуточного класса-переводчика. |
Рассмотрим паттерн Strategy на примере системы логирования. Допустим, ваше приложение должно отправлять критические ошибки в Slack, а обычные логи писать в файл. Вместо огромного if-else внутри сервиса, вы создаете интерфейс LogStrategy и две реализации: SlackLogStrategy и FileLogStrategy. Основной код просто вызывает strategy.log(message), а нужная реализация подставляется на основе уровня логирования. Это делает код расширяемым: завтра вы сможете добавить TelegramLogStrategy, не трогая существующую логику.
Разбор примера: Проектирование системы обработки заказов
Давайте пошагово разберем, как спроектировать сервис заказов, применяя архитектурные подходы.
Order, OrderItem, Product. Основной сценарий — PlaceOrder.OrderRepository (хранение), PaymentService (оплата), StockService (проверка склада). Мы не пишем реализацию, только контракты.OrderService мы пишем метод createOrder. Он сначала проверяет склад через интерфейс, затем создает запись о заказе, инициирует оплату и, в случае успеха, меняет статус.DiscountStrategy. Сервис заказов просто просит стратегию рассчитать финальную цену.OrderService, мы публикуем событие OrderCreatedEvent. На него подписаны SmsNotificationListener и WarehouseListener.BankApiAdapter, который берет на себя всю грязную работу по трансформации данных.Обработка ошибок и устойчивость системы
Архитектура — это не только про «красивые классы», но и про то, как система ведет себя при сбоях. Middle-разработчик должен проектировать механизмы Error Handling. Вместо того чтобы пробрасывать RuntimeException до самого верха, используйте иерархию бизнес-исключений. Это позволяет контроллеру точно знать, какой HTTP-статус вернуть: 404 (Not Found) для EntityNotFoundException или 400 (Bad Request) для ValidationException.
Важным аспектом является паттерн Circuit Breaker (Предохранитель). Если внешний сервис оплаты «лежит», нет смысла заставлять пользователя ждать 30 секунд тайм-аута при каждой попытке. Предохранитель «размыкает цепь» после нескольких неудач и сразу возвращает ошибку или запасной вариант (например, «Оплата временно недоступна, мы уведомим вас позже»), давая внешнему сервису время на восстановление.
Если из этой главы запомнить три вещи — это: