Чистая архитектура. Искусство разработки программного обеспечения Мартин Роберт

#ifndef _ACME_STD_TYPES

#define _ACME_STD_TYPES

#if defined(_ACME_X42)

    typedef unsigned int     Uint_32;

    typedef unsigned short   Uint_16;

    typedef unsigned char    Uint_8;

    typedef int              Int_32;

    typedef short            Int_16;

    typedef char             Int_8;

#elif defined(_ACME_A42)

    typedef unsigned long    Uint_32;

    typedef unsigned int     Uint_16;

    typedef unsigned char    Uint_8;

    typedef long             Int_32;

    typedef int              Int_16;

    typedef char             Int_8;

#else

    #error <acmetypes.h> is not supported for this environment

#endif

#endif

Заголовочный файл acmetypes.h не следует использовать непосредственно, иначе ваш код окажется тесно связанным с процессорами ACME. Вы думаете, что если используете процессор ACME, то какой вред он может причинить? Все просто, вы не сможете скомпилировать свой код, не подключив этот заголовочный файл. А если подключить его и определить символ _ACME_X42 или _ACME_A42, целые числа будут иметь неправильный размер при тестировании за пределами целевой платформы. Какое-то время это может не вызывать никаких проблем, но однажды у вас может появиться желание перенести свое приложение на другой процессор, и тогда вы обнаружите, что задача сильно усложнилась из-за отказа от переносимости и ограничения на подключение файлов, знающих о процессорах ACME.

Вместо использования acmetypes.h следует попробовать пойти более стандартным путем и использовать stdint.h. Но как быть, если в состав целевого компилятора не входит файл stdint.h? Вы можете сами написать этот заголовочный файл. Файл stdint.h может использовать acmetypes.h, когда выполняется компиляция для целевой платформы:

#ifndef _STDINT_H_

#define _STDINT_H_

#include <acmetypes.h>

typedef Uint_32 uint32_t;

typedef Uint_16 uint16_t;

typedef Uint_8 uint8_t;

typedef Int_32 int32_t;

typedef Int_16 int16_t;

typedef Int_8 int8_t;

#endif

Использование stdint.h во встраиваемом программном обеспечении и микропрограммах поможет вам сохранить код чистым и переносимым. Всякое программное обеспечение должно быть независимым от типа процессора, но этот совет годится не для всякой микропрограммы. В следующем фрагменте кода используются особые расширения языка C, позволяющие коду обращаться к периферийным устройствам в микроконтроллере. Продукт может быть оснащен этим микроконтроллером, поэтому вы можете использовать интегрированные в него периферийные устройства. Следующая функция выводит в последовательный порт строку с текстом "hi". (Этот пример основан на реальном коде.)

void say_hi()

{

  IE = 0b11000000;

  SBUF0 = (0x68);

  while(TI_0 == 0);

  TI_0 = 0;

  SBUF0 = (0x69);

  while(TI_0 == 0);

  TI_0 = 0;

  SBUF0 = (0x0a);

  while(TI_0 == 0);

  TI_0 = 0;

  SBUF0 = (0x0d);

  while(TI_0 == 0);

  TI_0 = 0;

  IE = 0b11010000;

}

Эта маленькая функция страдает множеством проблем. Первое, что бросается в глаза, — присутствие последовательности символов 0b11000000. Такая форма записи двоичных чисел очень удобна, но поддерживается ли она стандартным языком C? К сожалению, нет. Еще несколько проблем проистекают непосредственно из использования нестандартных расширений:

• IE: устанавливает биты разрешения прерываний.

• SBUF0: буфер вывода последовательного порта.

• TI_0: прерывание опустошения буфера передачи последовательного порта. Если операция чтения возвращает 1, это указывает, что буфер пуст.

Переменные с именами, состоящими из букв верхнего регистра, в действительности представляют механизмы доступа к встроенной периферии микроконтроллера. Если программе понадобится управлять прерываниями и выводить символы, вам придется использовать эту периферию. Да, это удобно, но это уже не язык C.

Чистая архитектура будет напрямую использовать эти средства доступа к периферии лишь в нескольких местах и только в пределах микропрограммы. Любой код, знающий о существовании регистров, автоматически превращается в микропрограмму и, соответственно, оказывается тесно связанным с конкретным оборудованием. Тесная связь кода с оборудованием мешает получить действующий код до получения стабильно работающего оборудования. Она также будет мешать переносить встраиваемое приложение на новый процессор.

Если вы используете микроконтроллер, подобный этому, ваша программа могла бы спрятать эти низкоуровневые функции за слоем абстракций процессора (Processor Abstraction Layer; PAL). Часть микропрограммы, находящаяся над слоем PAL, могла бы проверять платформу, на которой выполняется, и таким способом ослабить жесткость кода.

Операционная система — это деталь

Слой аппаратных абстракций (HAL) является насущной необходимостью, но достаточно ли его? Во встраиваемых системах, где отсутствует другое программное окружение, слоя HAL более чем достаточно, чтобы оградить код от избыточной зависимости от операционной среды. Но что, если встраиваемая система использует некоторую операционную систему реального времени (RealTime Operating System; RTOS) или встраиваемую версию Linux или Windows?

Чтобы дать коду шанс служить долго, операционную систему следует рассматривать как деталь и защищаться от зависимостей, связывающих с ней.

Программное обеспечение обращается к операционному окружению посредством операционной системы (ОС). ОС — это слой, отделяющий программное обеспечение от микропрограмм (рис. 29.5). Прямое использование механизмов ОС может стать источником проблем. Например, представьте,

Рис. 29.5. Дополнительный слой операционной системы

что производителя вашей RTOS купила другая компания и из-за этого выросла стоимость системы или упало ее качество. Или ваши потребности изменились, а используемая вами RTOS не обладает необходимыми возможностями. Вам придется изменить много кода. И это будут не просто синтаксические изменения, обусловленные сменой API, скорее всего, вам придется приспосабливать семантику кода к различным механизмам и примитивам новой ОС.

Чистая встраиваемая архитектура изолирует программное обеспечение от операционной системы, реализуя слой абстракции операционной системы (Operating System Abstraction Layer; OSAL), как показано на рис. 29.6. В некоторых случаях этот слой может иметь очень простую реализацию, выражающуюся в простой подмене имен функций. Но иногда может потребоваться полное обертывание некоторых функций.

Если вам доводилось переносить программное обеспечение с одной RTOS на другую, вы знаете, насколько трудно это дается. Если ваше программное обеспечение зависит только от слоя OSAL, но не зависит от ОС, вам потребуется только написать новый слой OSAL, совместимый с прежним. Что бы вы предпочли: изменить кучу сложного кода или написать новый код, определяющий интерфейс и поведение? Это даже не спорный вопрос. Я выбираю последнее.

Вас может обеспокоить проблема разбухания кода. Однако вам не о чем беспокоиться. Большая часть повторяющегося кода, обусловленного использованием операционной системы, будет сосредоточена в слое абстракции.  Такой повторяющийся код не будет вызывать больших накладных расходов.

Рис. 29.6. Слой абстракции операционной системы

Определяя слой OSAL, вы также получаете возможность привести свои приложения к некоторой общей структуре. Например, вы могли бы реализовать механизмы передачи сообщений и отказаться от ручного управления конкурентным выполнением в каждом потоке.

Слой OSAL может помочь создать точки для тестирования, чтобы прикладной программный код можно было тестировать без использования целевого оборудования и ОС. Программное обеспечение с чистой встраиваемой архитектурой поддерживает возможность тестирования вне целевой операционной системы. Удачно спроектированный слой OSAL предоставляет тот шов, или набор, точек подстановки, которые облегчат тестирование вне целевой среды.

Программирование с применением интерфейсов и возможность подстановки

Помимо добавления слоев HAL и, возможно, OSAL, внутри каждого из основных уровней (программное обеспечение, ОС, микропрограмма и оборудование) можно и должно применять принципы, описанные в этой книге. Эти принципы способствуют разделению ответственности, программированию с применением интерфейсов и возможности подстановки.

Идея многоуровневой архитектуры основывается на идее программирования с применением интерфейсов. Когда один модуль взаимодействует с другим посредством интерфейса, появляется возможность заменить одного поставщика услуг другим. Многим из вас наверняка приходилось писать свои небольшие версии printf для развертывания в целевой среде. Если интерфейс вашей функции printf совпадает с интерфейсом стандартной версии printf, вы можете использовать их взаимозаменяемо.

Главное правило — использовать заголовочные файлы как определения интерфейсов. Однако такой подход требует внимательно следить за тем, что помещается в заголовочный файл. Желательно не помещать в него ничего, кроме объявлений функций, а также констант и имен структур, необходимых функциям.

Не загромождайте интерфейсные заголовочные файлы структурами данных, константами и определениями типов, которые нужны только реализации. Это не просто вопрос беспорядка: подобный беспорядок приводит к нежелательным зависимостям. Ограничьте видимость деталей реализации. Исходите из предположения, что детали реализации будут изменяться. Чем меньше мест, где код знает детали, тем меньше кода вам придется просматривать и изменять.

Чистая встраиваемая архитектура позволяет выполнять послойное тестирование, потому что модули взаимодействуют посредством интерфейсов. Каждый интерфейс обеспечивает шов, или точку подстановки, для тестирования вне целевого окружения.

Принцип DRY и директивы условной компиляции

Одно из использований возможности подстановки, которое часто упускают из виду, связано с особенностями обработки разных целевых платформ или операционных систем программами на C и C++. Для этого часто используются директивы условной компиляции, включающие и выключающие сегменты кода. Я вспоминаю один особенно тяжелый случай, когда в телекоммуникационном приложении директива #ifdef BOARD_V2 встречалась несколько тысяч раз.

Повторяющийся код нарушает принцип «не повторяйся» (Don’t Repeat Yourself; DRY)[61]. Если #ifdef BOARD_V2 встречается один раз, в этом нет никакой проблемы. Но шесть тысяч раз — это большая проблема. Условная компиляция, идентифицирующая тип целевого оборудования, часто используется во встраиваемых системах. Можно ли от нее избавиться?

Можно, если реализовать слой аппаратных абстракций и скрыть определение типа оборудования в этом слое. Если слой HAL предоставляет набор интерфейсов, вместо условной компиляции можно взвалить всю работу на компоновщика (редактора связей) или использовать некоторую форму связывания во время выполнения для подключения программного обеспечения к оборудованию.

Заключение

Программисты, разрабатывающие встраиваемое программное обеспечение, могут многое взять из опыта разработки обычного программного обеспечения. Если вы занимаетесь разработкой встраиваемого программного обеспечения, то найдете в этой книге много мудрых и полезных советов и идей, которые пригодятся и вам.

Не позволяйте всему вашему коду превращаться в микропрограммы — это вредно для долгого и доброго здоровья вашего продукта. Возможность тестирования исключительно в целевом окружении — вредна для долгого и доброго здоровья вашего продукта. Следование принципам чистой встраиваемой архитектуры — полезно для долгого и доброго здоровья вашего продукта.

Часть VI. Детали

Глава 30. База данных — это деталь

С архитектурной точки зрения база данных не является сущностью — это деталь, которая не должна подниматься до уровня архитектурного элемента. Ее отношение к архитектуре программной системы сопоставимо с отношением дверной ручки к архитектуре здания.

Я понимаю, что эти слова могут показаться провокационными. Но верьте мне, я знаю, о чем говорю. А теперь позвольте мне пояснить: я не имею в виду модель данных. Структура, которую вы придаете данным в своем приложении, очень важна для архитектуры системы. Но база данных — это не модель данных. База данных — это часть программного обеспечения. База данных — это утилита, обеспечивающая доступ к данным. С архитектурной точки зрения эта утилита не имеет никакого значения, это низкоуровневая деталь — механизм. А хороший архитектор не позволяет низкоуровневым механизмам просачиваться в архитектуру системы.

Реляционные базы данных

Принципы реляционных баз данных были определены Эдгаром Коддом в 1970 году. К середине 1980-х годов реляционная модель разрослась и превратилась в доминирующую форму хранения данных. Такая популярность появилась не на пустом месте: реляционная модель элегантна, дисциплинированна и надежна. Это отличная технология хранения и доступа к данным.

Но какой бы блестящей, полезной и близкой к математике ни выглядела технология, она не перестает быть просто технологией. А значит, она — деталь.

Реляционные таблицы удобны для некоторых видов доступа к данным, но в упорядочивании данных по записям в таблицах нет ничего архитектурно значимого. Варианты использования в приложении не должны знать и заботиться о таких вещах. В действительности о табличной организации данных должны знать только низкоуровневые вспомогательные функции во внешних кругах архитектуры.

Многие фреймворки доступа к данным позволяют передавать записи и таблицы из базы данных в виде объектов через всю систему. Но такой способ действий является архитектурной ошибкой. Он связывает варианты использования, бизнес-правила, а в некоторых случаях даже пользовательский интерфейс с определенной реляционной структурой данных.

Почему системы баз данных настолько распространены?

Почему среди программных систем и корпоративных программных продуктов доминирующее положение заняли системы баз данных? Чем объясняется превосходство Oracle, MySQL и SQL Server? Если говорить одним словом — диски.

Пять десятилетий вращающийся магнитный диск был основой хранения данных. Несколько поколений программистов не знали иных форм хранения данных. Технология производства дисковых накопителей прошла долгий путь от огромных пакетов массивных пластин по 48 дюймов в диаметре, весивших тысячи килограммов и способных хранить 20 мегабайт, до тонких дисков по 3 дюйма в диаметре, весящих несколько граммов и способных хранить терабайт данных и даже больше. Это была сумасшедшая гонка. И на протяжении всей этой гонки программисты страдали от одного фатального свойства дисков: они работают слишком медленно.

Данные хранятся на диске на круговых дорожках. Дорожки делятся на секторы, хранящие определенное круглое число байтов, обычно 4 Кбайт. На каждой пластине может иметься несколько сотен дорожек, а в одном дисковом накопителе — десяток или около того пластин. Чтобы прочитать конкретный байт, устройство должно поместить головку на конкретную дорожку, дождаться, пока диск повернется так, что требуемый сектор окажется под головкой, прочитать все 4 Кбайт из сектора в ОЗУ, и затем вам останется только прочитать требуемый байт из ОЗУ. Но все это требует времени — миллисекунд времени.

Может показаться, что миллисекунды — это немного, но миллисекунда в миллион раз продолжительнее одного такта большинства процессоров. Если бы данные хранились не на диске, к ним можно было бы получить доступ не за миллисекунды, а за наносекунды.

Чтобы уменьшить задержку, вносимую дисками, нужны индексы, кэши и оптимизированные схемы запросов; а еще нужны какие-то регулярные средства представления данных, чтобы все эти индексы, кэши и схемы запросов знали, с чем они работают. Проще говоря, нужен доступ к данным и система управления. За годы развития эти системы разделились на два разных типа: файловые системы и системы управления реляционными базами данных (СУРБД).

Файловые системы опираются на понятие документа. Они поддерживают естественный и удобный способ хранения целых документов. Они хорошо работают, когда требуется сохранить или извлечь набор документов по имени, но не предлагают сколько-нибудь существенной помощи в поиске по содержимому документов. Вы легко найдете файл с именем login.c, но поиск всех файлов .c, содержащих переменную с именем x, будет долгим и трудным.

Системы баз данных ориентированы на содержимое. Они поддерживают естественный и удобный способ поиска записей по содержимому. Они очень хорошо связывают вместе несколько записей, имеющих общее содержимое. К сожалению, их преимущества теряются при хранении и извлечении непрозрачных документов.

Обе системы организуют хранение данных на диске так, чтобы обеспечить максимальную эффективность их сохранения и чтения, с учетом конкретных особенностей. Каждая имеет свою схему индексирования и организации данных. Кроме того, обе переносят соответствующие данные в ОЗУ, где ими можно быстро манипулировать.

Сохранятся ли диски?

Прежде занимающие доминирующие позиции, ныне диски превратились в вымирающий вид. В скором времени их ожидает судьба накопителей на магнитной ленте, гибких дисков и приводов компакт-дисков. Им на смену придет ОЗУ.

Спросите себя: когда диски исчезнут и все данные будут храниться в ОЗУ, как вы будете организовывать их? Вы предпочтете организовать их в таблицы и обращаться к ним, используя запросы на языке SQL? Или организовать их в файлы и обращаться к ним через систему каталогов?

Конечно нет. Вы организуете их в связные списки, деревья, хеш-таблицы, стеки, очереди или любые другие структуры данных и будете обращаться к ним, используя указатели или ссылки, потому что программисты так привыкли.

На самом деле, поразмыслив об этой проблеме, вы поймете, что все это вы уже делаете. Несмотря на то что данные хранятся в базе данных или в файловой системе, вы читаете их в ОЗУ и затем реорганизуете в удобные списки, множества, стеки, очереди, деревья или любые другие структуры по своему усмотрению. Очень маловероятно, что вы оставите данные в форме файлов или таблиц.

Детали

Такое положение дел объясняет, почему я говорю, что база данных — это деталь. Это лишь механизм перемещения данных взад-вперед между поверхностью диска и ОЗУ. База данных в действительности — не более чем большой мешок с битами, в котором мы храним свои данные. Но мы редко используем данные именно в такой форме.

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

А производительность?

Является ли производительность архитектурной проблемой? Конечно! Но в отношении хранения данных эту проблему можно полностью инкапсулировать и отделить от бизнес-правил. Да, нам нужно, чтобы данные быстро перемещались из хранилища и в хранилище, но это низкоуровневая проблема. Ее можно решить с помощью низкоуровневых механизмов доступа к данным. И она не имеет ничего общего с архитектурой системы.

История

В конце 1980-х годов я возглавлял команду разработчиков программного обеспечения в начинающей компании, которая пыталась создать и продать систему управления сетью, оценивавшую целостность телекоммуникационных линий T1. Система извлекала данные из устройств в конечных точках этих линий, а затем запускала ряд алгоритмов для обнаружения проблем.

Мы использовали платформы UNIX и хранили наши данные в простых файлах. Нам не нужна была реляционная база данных, потому что наши данные были слабо связаны между собой. Намного удобнее было хранить их в простых файлах, в виде деревьев и связных списков. Короче говоря, мы хранили данные в форме, упрощающей их загрузку в ОЗУ.

Мы наняли специалиста по маркетингу — хорошего знающего парня. Но он тут же заявил, что в нашей системе должна иметься реляционная база данных. Этого не требовалось для нормальной работы, и это не было технической проблемой — это была проблема маркетинга.

Для меня это было бессмысленно. Почему я должен превращать связные списки и деревья в кучу записей и таблиц, доступных через SQL? Почему я должен вводить накладные расходы и тратить деньги на приобретение массивной СУРБД, если простых файлов более чем достаточно? Поэтому я вступил в сражение с ним, пустив в ход зубы и когти.

В этой компании работал один инженер-электронщик, который направо и налево расхваливал СУРБД. Он имел свое, техническое обоснование необходимости СУРБД в нашей системе. За моей спиной он встретился с руководством компании, нарисовал изображение дома на шесте и спросил у руководства: «Хотели бы вы жить в доме на шесте?» Его посыл был прост: он считал, что СУРБД, хранящая таблицы в файлах, надежнее простых файлов, которые мы использовали.

Я боролся с ним. Я боролся с маркетологом. Я придерживался своих технических принципов перед лицом невероятного невежества. Я боролся, боролся и боролся.

В конце концов электронщик пошел на повышение и стал руководителем отдела программного обеспечения. Они втиснули СУРБД в эту несчастную систему. И в результате они оказались абсолютно правы, а я ошибался.

Нет, с технической точки зрения, заметьте, я был прав. Я был прав, что боролся с внедрением СУРБД в архитектурное ядро системы. Но я был не прав, потому что клиенты ожидали получить реляционную базу данных в нашей системе. Они не знали, что с ней делать. Они не представляли, как использовать реляционные данные из нее. Но это не имело значения: клиенты рассчитывали получить СУРБД. Она была галочкой, которую все покупатели программного обеспечения ставили в своих списках. Не было никакого технического обоснования — рациональность здесь вообще была ни при чем. Это была иррациональная, внешняя и полностью необоснованная потребность, но это не делало ее менее реальной.

Откуда это взялось? Причина кроется в высокоэффективных маркетинговых кампаниях, широко проводимых производителями баз данных в то время. Им удалось убедить высокопоставленных руководителей, что их корпоративные «активы данных» нуждаются в защите, а предлагаемые ими системы баз данных являются идеальным средством такой защиты.

Ныне мы наблюдаем похожие маркетинговые кампании. Слово «корпоративный» и понятие «сервис-ориентированная архитектура» имеют больше общего с маркетингом, чем с реальностью.

Что я должен был сделать в описанной ситуации? Я должен был прикрутить СУРБД к системе и дать ей узкий и безопасный канал доступа к данным, сохранив в ядре системы простые файлы. А что я сделал? Я ушел и стал консультантом.

Заключение

Организационная структура и модель данных являются архитектурно значимыми, а технологии и системы, перемещающие данные на вращающуюся магнитную поверхность, — нет. Системы реляционных баз данных, которые заставляют организовывать данные в таблицы и обращаться к ним посредством SQL, имеют гораздо больше общего с последними, чем с первыми. Данные — значимы, а база данных — это деталь.

Глава 31. Веб — это деталь

Вы занимались разработкой в 1990-х годах? Помните, как Всемирная паутина изменила все вокруг? Помните, с каким презрением мы смотрели на наши старые архитектуры клиент/сервер перед лицом новой сверкающей технологии «Веб»?

На самом деле Веб ничего не изменил. По крайней мере не должен был ничего изменить. Веб — это всего лишь последнее из последовательности колебаний, которые наша индустрия пережила с 1960-х годов. Эти колебания заставляют переносить всю вычислительную мощность то на центральные серверы, то на терминалы.

Только за последние десять лет (или около того), пока Веб занял заметное место, мы видели несколько таких колебаний. Сначала мы думали, что вся вычислительная мощность будет сосредоточена в фермах серверов, а браузеры будут просто отображать информацию. Затем мы начали добавлять апплеты в браузеры. Но нам это не понравилось, поэтому мы переместили динамическое содержимое обратно на серверы. Но это нам тоже не понравилось, и мы изобрели Веб 2.0, переместив почти всю обработку обратно в браузер, используя для этого Ajax и JavaScript. Мы зашли так далеко, что создали целые приложения, выполняющиеся в браузерах. И теперь мы снова возбуждены перемещением JavaScript обратно на сервер в виде Node.

(Вздох.)

Бесконечный маятник

Конечно, было бы неправильно думать, что колебания начались с появлением Веб. До Веб существовала архитектура клиент/сервер. До этого были центральные мини-компьютеры с массивами неинтеллектуальных терминалов. До этого были мейнфреймы с интеллектуальными терминалами (зеленого свечения, которые были очень похожи на современные браузеры). До этого были машинные залы и перфокарты...

Так выглядит история. Мы никак не можем определить, где должна быть сосредоточена вычислительная мощность. Мы все время то централизуем ее, то распределяем. И я думаю, что эти колебания будут еще продолжаться в течение какого-то времени.

С точки зрения истории развития информационных технологий Веб вообще ничего не изменил. Это просто одно из множества колебаний, которые начались еще до того, как многие из нас появились на свет, и продолжатся после того, как многие из нас уйдут на пенсию.

Однако как архитекторы мы должны быть нацелены на долгосрочную перспективу. Эти колебания — лишь кратковременные проблемы, которые не должны проникать в центральное ядро с нашими бизнес-правилами.

Я хочу рассказать вам одну историю о компании Q. Компания Q создала очень популярную систему управления персональными финансами. Это было приложение для настольного компьютера с очень удобным графическим интерфейсом. Мне нравилось пользоваться им.

Затем появился Веб. В следующей версии системы компания Q изменила пользовательский интерфейс — он стал выглядеть и действовать подобно веб-интерфейсу в браузере. Я был в шоке! Какой гений маркетинга решил, что программа управления персональными финансами, действующая на настольном компьютере, должна выглядеть как веб-браузер?

Конечно, я возненавидел новый интерфейс. Все остальные, видимо, тоже, потому что после выпуска нескольких версий компания Q отказалась от внешнего вида, напоминающего браузер, и вернула системе графический интерфейс обычного настольного приложения.

Теперь представьте, что в ту пору вы работали программным архитектором в Q. Вообразите, что какой-то гений от маркетинга убеждает высшее руководство в необходимости переделать весь пользовательский интерфейс, чтобы он напоминал веб-интерфейс. Что бы вы сделали? Точнее, что вы должны были бы сделать до этого момента, чтобы защитить приложение от этого гения маркетинга?

Вы должны были бы отделить бизнес-правила от пользовательского интерфейса. Я не знаю, как поступили архитекторы в Q. Я хотел бы когда-нибудь услышать их рассказ. Если бы в то время я работал там, я бы точно изолировал бизнес-правила от пользовательского интерфейса, потому что никто не знает, что взбредет в голову гениям маркетинга в следующий раз.

Теперь рассмотрим пример с компанией A, выпускающей замечательные смартфоны. Недавно она выпустила обновленную версию своей «операционной системы» (это так необычно — говорить об операционной системе внутри телефона). Кроме всего прочего модернизация кардинально изменила внешний вид всех приложений. Зачем? Я полагаю, что вновь не обошлось без какого-то гения маркетинга.

Я не эксперт по программному обеспечению в этих устройствах, поэтому не знаю, вызвали ли эти изменения какие-либо трудности для программистов приложений, выполняющихся на смартфонах компании A. Я надеюсь, что архитекторы в A и архитекторы приложений изолировали бизнес-правила и пользовательский интерфейс друг от друга, потому что всегда найдутся гении маркетинга, которые только и ждут, чтобы наброситься на что-то, созданное вами.

Вывод

Вывод из всего вышесказанного прост: пользовательский интерфейс — это деталь. Веб — это пользовательский интерфейс. То есть Веб — это деталь. И как архитектор вы должны размещать детали, как эта, за границами, отделяющими их от основной бизнес-логики.

Подумайте об этом с такой позиции: Веб — это устройство ввода/вывода. Еще в 1960-х годах мы поняли ценность методики создания приложений, не зависящих от устройств ввода/вывода. Побудительные мотивы такой независимости не изменились. Веб не является исключением из этого правила.

Или я не прав? Можно утверждать, что графический интерфейс, как и Веб, настолько уникален и богат, что абсурдно продолжать создавать архитектуры, независимые от устройства. Когда вы думаете о тонкостях проверки входных данных на JavaScript, особенностях технологии «перетащил и бросил» в AJAX или о том, какие еще виджеты/гаджеты вставить в веб-страницу, легко можно скатиться до утверждения, что независимость устройства непрактична.

Все это верно до определенной степени. Взаимодействия приложения с пользовательским интерфейсом обширны, многообразны и порой очень специфичны для данного интерфейса. Веб-приложение и веб-браузер взаимодействуют совершенно иначе, чем настольное приложение и графический интерфейс. Попытка абстрагироваться от этих взаимодействий, подобно тому, как в UNIX абстрагируются устройства, выглядит невероятной.

Но границу между пользовательским интерфейсом и приложением можно абстрагировать. Бизнес-логику можно рассматривать как набор вариантов использования, каждый из которых выполняет некоторую функцию от имени пользователя. Каждый вариант использования можно описать как комплекс входных данных, выполняемую обработку комплекс и выходных данных.

В какой-то момент в череде взаимодействий пользовательского интерфейса и приложения подготовка входных данных будет закончена и появится возможность выполнить вариант использования. По завершении полученные данные можно вернуть обратно, включив их во взаимодействия между пользовательским интерфейсом и приложением.

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

Заключение

Достичь такой абстракции очень непросто, и, скорее всего, потребуется несколько итераций, чтобы достичь желаемого результата. Но это возможно. А поскольку мир полон гениев от маркетинга, это не самое сложное, потому что часто бывает необходимо.

Глава 32. Фреймворки — это деталь

Фреймворки обрели большую популярность. Вообще говоря, это хорошая штука. Существует много бесплатных, мощных и полезных фреймворков.

Но фреймворки не определяют архитектуру, хотя некоторые пытаются.

Авторы фреймворков

Большинство авторов фреймворков предлагают результаты своего труда бесплатно, потому что хотят быть полезными для сообщества. Они хотят внести свой вклад. Это похвально. Однако, независимо от благородства мотивов, авторы не думают о ваших интересах. И не могут думать, потому что они не знают ни вас, ни ваших проблем.

Но они знают свои проблемы и проблемы своих коллег и друзей. И они пишут свои фреймворки для решения этих проблем, а не ваших.

Конечно, ваши проблемы, скорее всего, частично будут совпадать с этими другими проблемами. Иначе фреймворки не были бы столь популярны. Поэтому фреймворки могут быть очень полезными — в той мере, в какой существует совпадение.

Неравный брак

Отношения между вами и автором фреймворка чрезвычайно асимметричны. Вы должны обладать огромной приверженностью фреймворку, но его автор не несет никаких обязательств перед вами.

Задумайтесь над этим. Когда вы используете фреймворк, вы читаете документацию, предоставленную его автором. В этой документации автор и другие пользователи фреймворка рассказывают вам, как интегрировать ваше программное обеспечение с фреймворком. Обычно это означает, что вы должны обернуть фреймворк своей архитектурой. Автор рекомендует использовать базовые классы и импортировать инструменты фреймворка в свои бизнес-объекты. Автор настоятельно рекомендует вам привязать ваше приложение к фреймворку настолько, насколько это возможно.

Для автора тесная связь с его собственным фреймворком не является проблемой. Для автора такая связь желательна, потому что он обладает абсолютным контролем над этим фреймворком.

Более того, автор хочет привязать вас к фреймворку, потому что, привязавшись, будет очень трудно отвязаться. Ничто не подтверждает авторитет автора так, как группа пользователей, готовых порождать свои классы на основе базовых классов автора.

Фактически автор предлагает вам вступить в союз с фреймворком — выразить ему большую и долговременную приверженность. Но при этом сам автор ни при каких обстоятельствах не выражает ответной приверженности вам. Это однонаправленный союз. Вы принимаете на себя все риски и неудобства; автор фреймворка не берет на себя ничего.

Риски

Какие риски, спросите вы? Вот лишь некоторые из них.

• Часто архитектура фреймворков не отличается особой чистотой. Фреймворкам свойственно нарушать правило зависимости. Авторы предлагают вам наследовать их код в ваших бизнес-объектах — ваших сущностях! Они хотят внедрить свои фреймворки в самый внутренний круг. Как только это произойдет, убрать фреймворк оттуда будет почти невозможно. Надев обручальное кольцо на палец, он останется жить там.

• Фреймворк может помочь с реализацией первых возможностей приложения. Однако, достигнув некоторого уровня зрелости, ваш продукт перерастет фреймворк. Если вы надели это обручальное кольцо, то со временем заметите, что вам все чаще и чаще приходится бороться с фреймворком.

• Фреймворк может развиваться в направлении, бесполезном для вас. Вы можете столкнуться с необходимостью обновления до следующей версии, не несущей ничего, что могло бы пригодиться вам. Вы можете даже обнаружить, что старые особенности, которыми вы пользовались, исчезают или изменяются так, что вам трудно поспевать за ними.

• Может появиться новый, более удачный фреймворк, на который вы захотите перейти.

Решение

И каково решение?

Не заключать союзов с фреймворками!

О, вы можете использовать фреймворк — просто не привязывайтесь к нему. Держите его на расстоянии вытянутой руки. Рассматривайте фреймворк как деталь, принадлежащую одному из внешних кругов архитектуры. Не впускайте его во внутренние круги.

Если фреймворк предложит вам породить свои бизнес-объекты от его базовых классов, скажите «нет»! Определите прокси-классы и держите их в компонентах, являющихся плагинами для ваших бизнес-правил.

Не позволяйте фреймворкам проникать в ваш основной код. Внедряйте их в компоненты, которые лишь подключаются к вашему основному коду, как того требует правило зависимости.

Например, возможно, вам нравится Spring — хороший фреймворк внедрения зависимостей. Возможно, вы используете Spring для автоматического внедрения своих зависимостей. Это замечательно, но вы не должны окроплять аннотациями @autowired свои бизнес-объекты. Бизнес-объекты не должны знать о существовании Spring.

Но вы с успехом можете использовать Spring для внедрения зависимостей в своем компоненте Main. Этот компонент может знать о фреймворке Spring, потому что он является самым грязным и самым низкоуровневым компонентом в системе.

Объявляю вас

Есть некоторые фреймворки, с которыми вы просто обязаны вступить в союз. Если, например, вы пишете на C++, вам почти наверняка придется вступить в союз с STL — избежать этого очень сложно. Если вы пишете на Java, вы будете вынуждены вступить в союз со стандартной библиотекой.

Это нормально — но все равно это должно быть осознанное решение. Вы должны понимать, что, заключив союз между фреймворком и вашим приложением, вам придется придерживаться этого фреймворка в течение всего жизненного цикла этого приложения. В радости и в горе, в болезни и в здоровье, в богатстве и в бедности, отказавшись от всех остальных, вы будете использовать этот фреймворк. Этот шаг нельзя делать по легкомыслию.

Заключение

Встретившись с фреймворком, не торопитесь вступать с ним в союз. Посмотрите, есть ли возможность отложить решение. Если это возможно, удерживайте фреймворк за архитектурными границами. Может быть, вам удастся найти способ получить молоко, не покупая корову.

Глава 33. Практический пример: продажа видео

Теперь самое время объединить изученные нами правила и принципы создания архитектур и применить их для реализации учебного примера. Пример будет коротким и простым, но его будет вполне достаточно, чтобы продемонстрировать процесс, которому следует хороший архитектор, и принимаемые им решения.

Продукт

Для этого примера я выбрал продукт, с которым довольно близко знаком: программное обеспечение для веб-сайта, продающего видеоматериалы. Да, он напоминает cleancoders.com — сайт, где я продаю свои обучающие видеофильмы.

Основная идея проста. У нас есть партия видеофильмов, которые требуется продать. Мы продаем их через Веб физическим лицам и компаниям. Физические лица могут заплатить одну, более низкую, цену за потоковое видео, или другую, более высокую, за загрузку копий и их постоянное использование. Бизнес-лицензии распространяются только на потоковую доставку и приобретаются партиями, что позволяет предоставлять скидки.

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

Авторы видеофильмов должны предоставлять свои видеофайлы, сопровождая их описанием и вспомогательными файлами с контрольными вопросами, решениями, исходным кодом и другими материалами.

Администраторам необходимо добавлять новые видеоролики, добавлять видеоролики в серию и удалять их, а также устанавливать цены на различные лицензии.

Администраторы должны добавлять коллекции видеофильмов, удалять коллекции и устанавливать цены на различные лицензии.

Наш первый шаг в определении начальной архитектуры системы — выявление действующих лиц и вариантов использования.

Анализ вариантов использования

На рис. 33.1 изображена схема типичного анализа вариантов использования.

Рис. 33.1. Типичный анализ вариантов использования

На этой схеме изображены четыре действующих лица. В соответствии с принципом единственной ответственности, эти четыре действующих лица станут основными источниками изменений для системы. Добавление любых новых возможностей или изменение существующих будет выполняться ради обслуживания одного из действующих лиц. Поэтому мы должны разбить систему так, чтобы изменения, предназначенные для одного действующего лица, не затрагивали других.

На рис. 33.1 изображены не все варианты использования. Например, на схеме отсутствуют варианты, отвечающие за вход и выход. Это сделано специально, чтобы упростить задачу и ограничить ее размер. Если бы я включил все возможные варианты использования, эта глава могла бы превратиться в самостоятельную книгу.

Обратите внимание на варианты использования в центре на рис. 33.1, заключенные в пунктирные рамки. Это абстрактные[62] варианты использования. Абстрактным называется такой вариант использования, который определяет общую политику для других вариантов использования. Как видите, абстрактный вариант использования Просмотр каталога наследуют варианты Просматривает каталог как зритель и Просматривает каталог как покупатель.

С одной стороны, эту абстракцию можно было не создавать. Я мог бы убрать абстрактный вариант использования из схемы без ущерба для каких-либо особенностей продукта. С другой стороны, эти два варианта использования настолько похожи, что я счел разумным признать сходство и найти способ унифицировать его с самого начала.

Компонентная архитектура

Теперь, когда известны действующие лица и варианты использования, можно создать предварительную компонентную архитектуру (рис. 33.2).

Страницы: «« ... 678910111213 »»

Читать бесплатно другие книги:

Во многих организациях на пути внедрения Agile оказывается традиционный менеджмент. Командам тяжело ...
В поисках заколдованной алхимической рукописи Диана Бишоп, историк из Оксфорда и наследница ведьм, в...
Вторая книга из серии «Практический Fake Zen» содержит в себе несложный способ получения желаемых ре...
Толпа бежала к нему с криками, что он Король. Смятение, удивление — он ожидал чего угодно, только не...
Этот известный на Западе бестселлер уже двадцать лет помогает начинающим менеджерам проектов. В нем ...
Если вы постоянно устаете, раздражаетесь, у вас проблемы с кожей или волосами, вы ни с того ни с сег...