Что такое архитектура ПО, чем она отличается от "дизайна", и что же все-таки является входными данными для разработки архитектуры. Дискуссия на РСДН по данному вопросу была изумительна.
Оказывается, распространено мнение, что мол принципиальных отличий между "архитектурой" и "дизайном" нет. Некоторые идут дальше, и заявляют, что "архитектура" целиком подчинена [текущим] требованиям проекта, причем - функциональным требованиям. Наиболее радикальная точка зрения - архитектура ваще определяется пользовательским интерфейсом, или - внешним интерфейсом. Отдельная группа товарищей аппелирует к тяжелым определениям из мануалов и методологий, пытаясь найти истину в них.
Надо расставить точки над i по крайней мере в моей позиции. Надо для начала определить, что такое "архитектура", и разобрать это определение на конкретных примерах. То, чего, почему-то, сильно не любит публика на РСДН. Странно, но по факту оказывается так. Ок, essense of software architecture минут за 10.
Дизайн - это структура вашей программы. Ее подразбиение на составные части. Ваш подход к решению задачи, которая диктуется текущими требованиями. Хороший дизайн легко понять, и дешево модифицировать. Обыкновенно, локальные правки в хороший дизайн можно вносить не зная всей картины, без риска все сломать.
"Дизайн" понимают и как протяженный во время процесс, и как некий артефакт, в который можно ткнуть пальцем (хоть и сложно, но как-то само собой подразумевается, что можно). Цель "дизайна" (проектирования) как процесса - получить понимание того, как мы будем структурировать нашу систему, какова будет ее декомпозиция. Результат - собственно описание декомпозиции, из каких компонент система состоит, и каковы связи между ними.
Декомпозиция системы бывает структурная, и функциональная. Таким образом, к дизайну относятся модели вроде класс-диаграмм, схем зависимости модулей, а также dataflow-диаграмм, показывающих "поток данных", то есть данные, и функциональные зависимости между ними. В UML нет dataflow-диаграмм, и данная техника малознакома "объектно-ориентированной" публике. Однако, если вы нанесете на стрелки ассоциаций на диаграмме классов вызовы, которые дергаются по данной ассоциации, вы по смыслу получите практически ее, dataflow-model.
Если для того, чтоы понять, как мы будем структурировать систему, надо кодировать (такое бывает) - то это все равно "проектирование", а не кодирование, поскольку цель процесса на данном этапе - понять, как мы будем структурировать систему, а не получить работоспособную программу.
Полезно отделять "детальный дизайн" от "дизайна". Отличие очень простое - в детальном дизайне явно присутствует время, и/или точные спецификации структур/форматов данных, в то время как в описании просто "дизайна" явно не присутствуют ни структуры данных, ни временная последовательность - он, как уже отмечалось, статичен, и задает только структуру. Решениями уровня детального дизайна является выбор применяемых алгоритмов и структур данных, и наброски критичных алгоритмов. Модели уровня "детального дизайна" - это sequence диаграммы, описания конечных автоматов, блок-схемы алгоритмов, "псевдокод", описание форматов, структур, и типов данных (то есть header-файлы). Еще к детальному дизайну можно отнести деятельность по прототипированию proof of the concept.
"Дизайн", выражающийся в объектной декомпозиции, должен давать некоторую независимость от выбора алгоритмов и структур данных. "Детальный дизайн" близок к коду. В некоторых языках, таких как Хаскель, разница между кодированием и детальным дизайном призрачна и малосущественна.
Для того, чтобы выловить все ошибки в "дизайне", часто надо сделать "детальный дизайн". Либо вы выделяете на него время (пусть вы делаете его в уме - неважно), и тогда - имеете возможность ловить ошики дизайна до кодирования, либо - пропускаете эту деятельность, и ловите ошибки при кодировании. Опять же, написание прототипа не есть кодирование, поскольку имеет разные цели.
Идут ли данные "деятельности" последовательно? Ну, то есть, всегда ли грамотный инженер-программист сначала усиленно думает, а потом начисто делает? Вообще, подумать наперед дешевле. Но на практике далеко не всегда, так как знать все в принципе невозможно, и в этих случаях надо пробовать, смотреть на результат, и думать.
Инженерия - экспериментальная дисциплина, и наперед все предусмотреть и продумать невозможно. И чем более рискованный и инновационный у вас проект, тем невозможнее это невозможно. Информации не хватает - надо пробовать, и анализировать результат, эксперимент - критерий истины.
Значит ли это, что не надо пытаться думать наперед? Тоже нет. Это значит, что не надо обманывать себя. Если для получения понимания надо написать код, пишите, начиная с самой непонятной части, продвигаясь маленькими шажками, держа в голове, что цель этой деятельности на раннем этапе - получить в первую очередь это самое понимание, а не отлаженный код.
Что же такое "архитектура", и почему это не "дизайн". Архитектура - это тот набор ограничений, специфичных для системы или проекта, набор своего рода "правил проектирования", которому вы следуете, когда делаете дизайн. В чем выражаются эти ограничения - используемые технологии, некоторые договоренности касательно структурирования кода (определенный набор слоев и/или сервисов например), и диктуемая этими ограничениями необходимость задействования некоторого кода (фреймворка, например), уже не так принципиально.
Принципиально то, что дизайн нацелен на реализацию конкретного набора требований, в то время как архитектура - на реализацию широкого класса требований. Архитектура - это "стратегический дизайн", общие принципы построения и структурирования вашей системы, ее философия. Архитектура - это то, что вы не сможете "отрефакторить", и из-за чего вы будете переписывать систему с нуля, если оно плохое. Дизайн - это мясо, архитектура - скелет. Архитектура должнаудешевить разработку в долговременной перспективе, чтобы система не треснула по швам при добавлении очередной порции требований.
Короче говоря, "архитектура" - это когда вы думаете сильно вперед.
Является ли выбор базовых технологий архитектурным решением? Безусловно. Решая, что вы пользуетесь MS SQL, вы во-первых, выделяете слой хранения данных как отдельный слой, во-вторых - решаете, что у вас в этом слое будет реляционная модель. И сразу ограничиваете себе класс решаемых задач - MS SQL не заточен под большой поток быстрых look-up запросов. Дает вам это ограничение, кроме ясности, тоже много - вся мощь MS SQL, его движка обработки данных, и зранимых процедур.
Или, выбирая, например, BerkleyDB, внедренный в процесс, вы также характеризуете себя как разумного человека. Но только в том случае, если хорошо представляете себе класс задач, в котором будет применяться ваш компонент, как вы будете его использовать, для чего, и ключевые требования к нему.
Скажем, в задаче обработки биржевых котировок "вся мощь SQL" вам не особо поможет (join-ов почти нет), зато сильно поможет возможность реализации собственного алгоритма сжатия котировок (их реально много), и отсутствие оверхэда на передачу данных через границу процесса (он заметен, когда запрос возвращает примерно столько же данных, сколько на входе).
Разрабатываете ли вы сервис сами, или подбираете сторонней разработки - не так принципиально. Осознанный подбор базовых технологий сторонней разработки, также, как и принятие решения о разработке собственных, требует грамотной постановки требований и хорошего понимания класса задач. Выбор базовых технологий и принципов, на которых будет построена ваша система - это и есть тот самый "стратегический дизайн".
Однако, в чем на практике выражается разница? Вот, допустим, кто-то не отделяет архитектуру от дизайна, и считает, что так и надо. К чему это приведет? Посмотрим на конкретном примере
Наша задача - написать media player. Допустим, под Linux. Ну, знаете, iTunes там, или Windows Media Player? Который, помимо прочего, сможет проигрывать цифровое эфирное телевидение в стандарте DVB. Т
ак вот, в основе архитектуры будет устройство Media Processing Engine, крупный безгуевый компонент, который обеспечит собственно декодирование медии, и поверх которого будет сделан ГУЙ. Это мы уже приняли архитектурное решение, разделив обработку медиапотока от управления и презентационного уровня. А прием цифрового ТВ будем делать посредством стандартной драйверной подсистемы LinuxDVB. Но фокус не в этом. Смотрим дальше - мы близки к моменту истины.
Существует множество разнообразных Media Engine, которые мы можем взять за основу. Или сделать свой собственный. Рассмотрим два кандидата - Helix DNA Client, и GStreamer, и проследим, какого рода последствия тот или иной выбор окажет на структуру нашей системы. Требования, внимание! - одни и те же! Наш Media Engine должен уметь показывать видео с нового типа источника - LinuxDVB, мы должны задействовать аппаратный декодер видео нашей разработки.
Итак. На первый взгляд по интересной нам функциональности они одинаковы. Детали: HelixDNAClient - построен на базе COM (!), содержит абстракцию от файловых систем, устройств, и API. GStreamer - построен на основе GObject и DBus, и в целом подталкивает нас к тому, чтобы строить плеер на основе GTK+. Ну, типа, особенности, ладно. Самое интересное будет, когда мы заглянем внутрь.
В HelixDNAClient первична обработка потокового аудио-видео, полученного по сети в реальном времени. Центром его архитектуры, стержнем, является получение нескольких медиапотоков по протоколу RTP, их декодирование, синхронизация, и показ на абстрактных устройствах вывода. Но он умеет проигрывать и файлы! Да-да! Сделано это вот как: когда вы читаете файл, вы разбираете его формат, формируете COM-объекты, соответствующие распарсеным RTP-пакетам (!) и они подсовываются внутрь движка, типа мы их с сети получили. При этом, обработка самих форматов файлов сделана зашибись как абстрактно, даже файловая система абстрагирована.
Как нам в рамках данной архитектуры научить его проигрывать DVB, получаемый с тюнера в реальном времени? Элементарно. :) Пользуясь его абстракциями, пишем свою "файловую систему", представляющую тюнер (или канал) в виде такого специального файла, свой "файловый формат", в котором - заворачиваем все в RTP-пакеты! :).
DVB File System Plugin -> (transport stram "file") -> DVB File Format PlugIn ->(RTP Packets) -> Helix Core -> ...
GStreamer построен не так. Пользуясь им, вы строите медиаконвейр, состоящий из элементов, увязанных в произвольный однонаправленный граф (без циклов). Элементы равноправны, и конфигурируются на лету. GStreamer проследит за синхронизацией потоков. Что нам надо сделать, чтобы он научился проигрывать DVB? Написать элемент-источник DVB, который воткнется в элемент-разборщик транспортного потока DVB (тоже написать), и, собственно, все. При запуске, сконфигурировать граф обработки так, что эта пара элементов будет стоять в начале графа.
DVB Source -> (transport stream) -> DVB Demux -> (audio), (video) ...
На самом деле, GStreamer уже умеет это делать, компоненты для DVB в ем есть. Но это не важно. Важно то, что GStreamer проще сам по себе. Helix DNA Client - кусок слипшегося дерьма, GStreamer - набор слабосвязанных компонент. И он делает вашу систему проще. Вам не надо изворачиваться, и изобретать способы, как добавить новую функциональность - это делается прямолинейно. Однако, у кого-то возникнут сомнения, ну по разному все делается немного, но и что, собственно?
Добавляем фичу Time Shifting. При постановке на паузу живой трансляции, мы должны писать транспортный поток на диск. При снятии с паузы - продолжить воспроизведение с той точки, на которой встали, и все равно писать на диск то, что приходит с эфира.
Helix DNA Client. Элементарно! :) Врезаемся в "файл", если внутренний буфер переполняется, и из нас долго не читают - так мы эта, на диск вытесняем буфер.
GStreamer. Пишем третий элемент, который будет делать то же самое, что написано абзацем выше, только, сами понимаете, с произвольным источником.
В результате имеем. В GStreamer мы можем воткнуть TimeShifter куда угодно - и в тракт воспроизведения DVB, и в случае, если мы принимаем видео по IPTV (транспортный поток DVB внутри RTP). Только и делов - переконфигурировать граф. А это вдруг оказывается важным требованием - гибридные устройства, принимающие как эфирный цифровой сигнал, так и IP, нынче входят в моду.
В Helix - что надо сделать, чтобы реализованный нами Time Shifting работал в случае IPTV? Врезать его в другое место - в сам Helix Core? Валяйте, операция на спинном мозге без наркоза, учебники по хирургии уже купили :). После нее вам придется ломать голову, как реализовать цифровой видеомагнитофон (запись программы по расписанию, желательно в том же формате, в котором она приходит). :)
Архитектуры Helix DNA Client, и GStreamer являются отличными иллюстрациями к данной дискуссии. Не важно, выбираете ли вы сторонний компонент, или решаете делать свой - вы принимаете архитектурное решение тогда, когда выбираете общие правила структурирования системы - что будет ее "скелетом". Вы можете не думать об архитектуре, то есть - думать о ней как о дизайне, и строить ее на базе текущих требований. Получится примерно так, как у парней из RealMedia, авторов Helix. Ну, то есть на базе такого фреймворка можно будет реализовать широкий класс приложений, таких, которые будут называться RealOne Player. :) А ведь парни старались, между прочим.
Собственно, вот. Если и после такого примера что-то будет непонятно - ну тогда я уже не знаю как объяснить.