diff --git a/README.ru b/README.ru index 91a18a6..8484bb3 100644 --- a/README.ru +++ b/README.ru @@ -14,3 +14,43 @@ * Подсистема обработки форм (генерация HTML формы по объекту, валидация форм, показ недозаполненной формы) Для соединения с БД используется пул соединений, тот же модуль используется для соединений с cache backend. + +## Общие замечания об архитектуре + +Архитектура фреймворка в общих чертах соответствует MVC. Модели описывают таблицы в БД (но могут иметь дополнительные поля, не хранящиеся в БД). В качестве слоя View выступает система шаблонов TEngine. Контроллеры занимаются выбором нужных данных и передачей этих данных в шаблоны. + +Приложение обычно будет импортировать из фреймворка модули Framework.API и Framework.Utils. Модуль API ре-экспортирует интерфейсы некоторых внутренних модулей фреймворка и экспортирует "обёртки" для других модулей. + +## Диспетчер URL + +Диспетчер URL конфигурируется с помощью типа URLConfig. Значение этого типа составляется с помощью операторов-комбинаторов, экспортируемых модулем Framework.Urls (и ре-экспортируемых модулем Framework.API). + +Обработка URL в диспетчере происходит следующим образом. Сначала URL разбивается на части, разделённые косой чертой. Далее диспетчер проходит по конфигурации, пытаясь отождествить текущий фрагмент URL с "кусочком" конфигурации, и если это удаётся - вызывается указанный в конфигурации контроллер. + +Например, конфигурация "blog" --> allposts означает, что все URL, начинающиеся с /blog/, будет обрабатывать контроллер allposts. А конфигурация "blog" // "archive" --> archive означает, что URL, начинающиеся с /blog/archive/, обрабатывает контроллер archive. Оператор <|> комбинирует конфигурации по принципу "если не первое, то второе". Например, + +urlconf = "blog" // "archive" --> archive + <|> "blog" --> allposts + +будет работать следующим образом: сначала будет проверено, не начинается ли URL с /blog/archive/; Если начинается, то будет вызван контроллер archive; Если же нет - контроллер allposts. При этом любой контроллер может "отвергнуть" переданный ему URL (см. ниже); Тогда диспетчер будет пробовать сопоставлять url со следующими правилами в конфигурации. Если ни одно правило не подошло (или все подходящие контроллеры отвергли этот url), будет порождена ошибка HTTP 404. + +## Контроллеры + +Контроллеры приложения (и некоторые другие части) выполняются в монаде Controller. Controller является экземпляром классов Monad, MonadIO и MonadReader. Тип Controller имеет три типа-параметра: тип передаваемой конфигурации (обычно это ActionConfig или StaticConfig) и два возможных типа возвращаемого значения. + +Контроллеры (Controller s r a) можно комбинировать (monadic bind) тремя способами. Во-первых, просто выполнение действий одно за другим, как в монаде IO. Такой контроллер возвращает тип a (третий тип-параметр). Во-вторых, контроллер (действие) может завершиться вызовом функции returnNow вместо return, тогда последующие контроллеры (действия) в цепочке не будут вычисляться, а вся цепочка вернёт значение типа r (второй тип-параметр). И в-третьих, в любой момент можно прервать вычисление вызовом действия reject. В контроллере приложения это будет означать "Я не буду обрабатывать этот URL". + +Контроллеры приложения возвращают тип Controller ActionConfig HttpResponse HttpResponse, для которого определено сокращение HttpController. Т.е. в качестве конфигурации они принимают ActionConfig (см. ниже) и в любом случае возвращают HttpResponse (сформированный объект-ответ). + +Контроллеры-действия, определённые в фреймворке, часто имеют тип AController a, что раскрывается как Controller ActionConfig HttpResponse a. Т.е. они используют ActionConfig в качестве конфигурации, а возвращают либо HttpResponse, либо некий тип a. Например, действие tryReturnFromCache имеет тип AController (), т.е. оно может вернуть либо уже сформированный ответ (при этом последующие действия в цепочке не будут выполняться, контроллер вернёт этот ответ), либо "ничего", т.е. () (и будет продолжено выполнение остальных действий в цепочке). + +## Конфигурация + +Параметры, которые могут понадобиться контроллеру или другой части приложения, можно разделить на две группы. Параметры первой группы не меняются во время работы приложения. Это, например, путь к статическим файлам, параметры соединения с БД и др. Параметры второй группы свои для каждой обработки HTTP-запроса. Это, например, собственно объект запроса HttpRequest, открытое соединение с БД, канал для лога и др. Параметры первой группы (статические) содержатся в структуре StaticConfig. Бòльшая их часть читается из файла конфигурации. Параметры второй группы содержатся в структуре ActionConfig, равно как и указатель на структуру StaticConfig. + +Т.к. Controller является экземпляром MonadReader, то доступ к конфигурации можно получить с помощью стандартных действий ask и asks, например: + +controller = do + rq <- asks request -- Получить объект-запрос + doSomethingWith rq + ... diff --git a/TODO b/TODO index 986776d..609f739 100644 --- a/TODO +++ b/TODO @@ -6,7 +6,6 @@ TODO * Более высокоуровневый интерфейс для кэша - чтоб было легко закэшировать результат всей функции; * Соответственно, простые средства для инвалидации кэша; * Более продвинутые и высокоуровневые функции генерации SQL; - * (?) Чтение конфига из файла либо удобный EDSL для конфига; * (?) Слой абстракции от диалекта SQL; * (?) Генерация структуры Form по Model (с возможностью переопределить, или просто не использовать); * (?) Автоматические CRUD-контроллеры; @@ -14,6 +13,7 @@ TODO * (!) Документация ко всей этой красоте. * Протестировать поддержку PUT web-сервером; + * [DONE] Чтение конфига из файла либо удобный EDSL для конфига; * [DONE] Перенести текущий правленный Network.Shed.Httpd в дерево проекта (написать свой?); * [DONE] Лучше интегрировать Httpd в движок, в частности - чтоб средствами движка писал логи итп; * [DONE] Лучше формализовать формат запроса (reqMethod=="GET" -> reqMethod==GET итп), вероятно, следует привести в соответствие с Network.HTTP;