Я хочу стать архитектором ПО:
Это хорошая цель для разработчика
Я хочу управлять командой и принимать важные решения о базах данных, фреймворках и веб-сервисах и все такое.
Хм. Ну, тогда ты вовсе не хочешь стать архитектором ПО.
Конечно хочу! Я хочу быть тем человеком, который принимает все важные решения.
Это хорошо, но ты не перечислил важных решений. Ты перечислил решения, не играющие особой роли.
В смысле? База данных – это не важное решение? Знаешь, сколько мы денег тратим на них?
Скорее всего слишком много. И нет, база данных – это не одно из самых важных решений.
Как можно такое говорить? База данных находится в самом центре системы! Там собраны все данные, они сортируются, индексируются и к ним осуществляется доступ. Без нее не будет системы!
База данных это просто устройство ввода-вывода. Так получилось, что она предоставляет некоторые полезные инструменты для сортировки, запросов и отчетов, но все это – вспомогательные аспекты в рамках системной архитектуры.
Вспомогательные? Это безумие.
Да, вспомогательные. Бизнес-правила твоей системы, возможно, используют некоторые из этих инструментов. Но они не существенны для бизнес-правил. Если нужно, то можно заменить эти инструменты другими инструментами, но бизнес-правила останутся теми же.
Ну, да, но придется переписывать весь код, потому что он использует инструменты оригинальной базы данных.
Ну, в этом твоя проблема.
Что ты имеешь ввиду?
Твоя проблема в том, что ты веришь, что бизнес-правила зависят от инструментов базы данных. Это не так. Во всяком случае, они не должны зависеть если ты разработал хорошую архитектуру.
Это безумие. Как я могу создавать бизнес-правила, которые не используют инструменты, которые нужно использовать?
Я не сказал, что они не используют инструменты базы данных. Я сказал, что они не должны зависеть от них. Бизнес-правила не должны знать какую конкретную базу данных ты используешь.
Как заставить бизнес-правила использовать инструменты без знания, что это за инструменты?
Нужно инвертировать зависимость. У тебя база данных зависит от бизнес-правил. Сделай так, чтобы бизнес-правила не зависели от базы.
Ты говоришь какую-то белиберду.
Наоборот. Я говорю на языке архитектуры ПО. Это принцип инверсии зависимостей. Низкоуровневые аспекты должны зависеть от высокоуровневых аспектов.
Больше белиберды! Высокоуровневые аспекты (я так понимаю, ты говоришь про бизнес-правила) вызывают низкоуровневые (я так понимаю, ты имеешь ввиду базу данных). Так что высокоуровневые модули зависят от низкоуровневых так же, как вызывающие зависят от вызываемых. Все это знают!
В рантайме это правда. Но в момент компиляции нам нужно инвертировать зависимость. Исходный код высокого уровня не должен упоминать исходный код низкого уровня.
Ну блин! Нельзя вызвать что-то, не упоминая его.
Конечно можно. В этом суть объектной ориентации.
Суть объектной ориентации в моделировании реального мира, в комбинации данных и функций в связанные объекты. В организации кода в интуитивную архитектуру.
Так тебя научили?
Все это знают. Очевидно, что это правда.
Не сомневаюсь. Не сомневаюсь. И все таки, используя принципы объектной ориентации можно на самом деле вызвать что-то не упоминая этого.
OK. Как?
Ты знаешь, что в объектно-ориентированном дизайне объекты посылают сообщения друг другу?
Да. Конечно.
А ты знаешь, что отправитель сообщение не знает тип получателя?
Это зависит от языка. в Java отправитель знает как минимум базовый тип получателя. В Ruby отправитель знает как минимум то, что получатель может обработать посланное сообщение.
Да. Но ни в одном, ни в другом случае отправитель не знает конкретного типа получателя.
Ага. ОК. Да.
Следовательно, отправитель может быть причиной запуска функции в получателе, при этом не упоминая тип получателя.
Ага. Правильно. Это я понимаю. Но отправитель все еще зависит от получателя.
В рантайме, да. Но не при компиляции. Исходный код отправителя не упоминает и не зависит от исходного кода получателя. Исходный код получателя зависит от исходного кода отправителя.
Нееее. Отправитель все еще зависит от класса, которому посылается сообщение.
Возможно, нам нужны примеры. Я буду писать на Java. Сначала пакет отправителя:
package sender;
public class Sender {
private Receiver receiver;
public Sender(Receiver r) {
receiver = r;
}
public void doSomething() {
receiver.receiveThis();
}
public interface Receiver {
void receiveThis();
}
}
Теперь пакет получателя.
package receiver;
import sender.Sender;
public class SpecificReceiver implements Sender.Receiver {
public void receiveThis() {
//do something interesting.
}
}
Заметь, что получатель зависит от отправителя. Также заметь, что SpecificReceiver зависит от Sender. И ничего в отправителе не знает о получателе.
Да, но это читерство. Ты вставил интерфейс получателя в класс отправителя.
Теперь ты начинаешь понимать.
Понимать что?
Принципы архитектуры, конечно же. Отправители владеют интерфейсами, которые получатели должны реализовывать.
Если это значит, что придется использовать вложенные классы, то...
Вложенные классы это всего лишь один из способов добиться цели. Есть другие.
ОК, погоди. Причем тут базы данных? Мы начали с этого.
Давай взглянем на другой код. Вот первое бизнес-правило:
package businessRules;
import entities.Something;
public class BusinessRule {
private BusinessRuleGateway gateway;
public BusinessRule(BusinessRuleGateway gateway) {
this.gateway = gateway;
}
public void execute(String id) {
gateway.startTransaction();
Something thing = gateway.getSomething(id);
thing.makeChanges();
gateway.saveSomething(thing);
gateway.endTransaction();
}
}
Это бизнес-правило особо ничего не делает.
Это просто пример. У тебя скорее всего много таких классов, реализующих разные правила.
ОК, что это за Gateway
там?
Он предоставляет все метода доступа к данным, которые использует бизнес-правило. Вот его реализация:
package businessRules;
import entities.Something;
public interface BusinessRuleGateway {
Something getSomething(String id);
void startTransaction();
void saveSomething(Something thing);
void endTransaction();
}
Заметь, что это не пакет businessRules.
Ага, ОК. А что за класс Something
?
Он представляет простой бизнес-объект. Я положил его в пакет под названием entities.
package entities;
public class Something {
public void makeChanges() {
//...
}
}
И, наконец, вот реализация BusinessRuleGateway. Этот класс знает о самой базе данных:
package database;
import businessRules.BusinessRuleGateway;
import entities.Something;
public class MySqlBusinessRuleGateway implements BusinessRuleGateway {
public Something getSomething(String id) {
// use MySQL to get a thing.
}
public void startTransaction() {
// start MySQL transaction
}
public void saveSomething(Something thing) {
// save thing in MySQL
}
public void endTransaction() {
// end MySql transaction
}
}
Опять же, заметь, что бизнес-правила вызывают базу данных во время исполнения, но во время компиляции это делает пакет database. Он упоминает и зависит от пакета thebusinessRules.
Ок, ок, кажется, я понял. Ты просто используешь полиморфизм чтобы скрыть реализацию базы данных от бизнес-правил. Но все еще нужен интерфейс, который предоставляет бизнес-правилам доступ ко всем инструментам базы данных.
Нет, вовсе нет. Мы не пытаемся предоставить бизнес-правилам все инструменты базы данных. Наоборот, наши бизнес-правила создают интерфейсы только для того, что им требуется. Реализация этих интерфейсов уже может вызывать подходящие инструменты.
Ага, но если бизнес-правилам нужны все эти инструменты, то придется положить их все в интерфейс gateway
.
Ах. Вижу, что ты все еще не понимаешь.
Не понимаю чего? Мне кажется, все предельно ясно.
Каждое бизнес-правило определяет интерфейс лишь для необходимого ему способа доступа к данным.
Стоп. Что?
Это называется принцип разделения интерфейса. Каждый класс бизнес-правила будет использовать только часть механизмов базы данных. Поэтому каждое бизнес-правило предоставляет интерфейс, дающий ему доступ только к этим механизмам.
Но это значит, что придется иметь дело с кучей интерфейсов, и кучей маленьких классов реализации для вызова других классов базы данных.
Хорошо. Вижу, что теперь ты начинаешь понимать.
Но это бардак и трата времени! Зачем такое делать?
Чтобы содержать все в чистоте и экономить время.
Ой, все! Это просто куча кода ради кода.
Наоборот, эти важные архитектурные решения позволят откладывать нерелевантные решения.
Что ты имеешь ввиду?
Помнишь, ты начал с того, что хотел стать архитектором ПО? Ты хотел принимать все самые важные решения?
Да, этого я и хочу.
Среди этих решений были база данных, веб-сервер и фреймворки.
Ага, и ты сказал, что это не важные решения. Ты сказал, что они не играют особой роли.
Верно. Не играют. Важные решения, которые принимает архитектор ПО, позволяют НЕ принимать решения по поводу базы данных, веб-сервера и фреймворков.
Но нужно принять эти решения вначале!
Нет, не нужно. На самом деле, нужно сделать так, чтобы была возможность принимать эти решения намного позже в цикле разработки – когда будет больше информации.
Горе – это архитектор, который преждевременно выбирает базу данных, а потом понимает, что плоской файловой структуры было бы достаточно.
Горе – это архитектор, который преждевременно выбирает веб-сервер, а потом понимает, что команде нужен был простой socket-интерфейс.
Горе – это команда, чей архитектор преждевременно навязывает на нее фреймворк, а потом понимает, что фреймворк предоставляет не нужные им возможности и добавляет ограничения, с которыми невозможно жить.
Благословенна команда, чьи архитекторы предоставили способ откладывания всех этих решений до момента, когда у команды будет достаточно информации для их принятия.
Благословенна команда, чьи архитекторы так хорошо изолировали их от ресурсоемких устройств ввода-вывода и фреймворков, что команда способна создавать быстрые и легкие среды тестирования.
Благословенна команда, чьи архитекторы волнуются о по-настоящему важных вещах, и откладывают не важные.
Ерунда. Я ничего не понимаю.
Ну, возможно, поймешь лет через десять… Если не перейдешь в менеджеры.
Автор: freetonik