-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathPLANS
269 lines (208 loc) · 18.7 KB
/
PLANS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
Пропущённое из книги:
- new
- server
- generate controller
Что надо тактически:
- парсить в mlt типы данных -- список, хеш, тело
Плохо то, что могут понадобиться вложенные хеши, списки и прочее непростое,
тогда непонятно, как мапить их на камлофункции. Как вариант, если директива
простая, без вложенного, то мапить на dir_slh (string hash list), а сложнее --
на dir_gen : expr -> ..., где
expr = [ String of string | Hash of list (string * expr) | List of list expr ]
Что надо принципиально:
Для моделей должны быть отдельные модули. Чтобы валидация и что около неё
не перекрывалось промеж моделями, хотя бы.
Для config/database:
Уметь сгенерить доку: в шаблон вставить пустую строку вместо database.ml,
натравить ocamldoc.
Уметь указать реальный database.ml вида
Pg.conn_info ~host:.. ~user:.. ~pass:..
Тут же продумать: а ведь conn_info имеют разный тип для разных бд.
Получается, надо хранить [ Ci_pg of Dbi_pg.conn_info | Sqlite of Dbi_s of .. ]
для хранения инфы о соединении, далее вызывать нужный new Dbi_xx.connection,
а дальше -- как бы сделать так, чтобы код не требовал указаний типов?
Видимо решение тут -- подгонять dbi под общий интерфейс и использовать тут
только его.
Для миграций:
возможность указать файлы, прилепить им в начало
module <Filename> = struct let __filename = "filename.ml" open Xxx open Yyy
<filename.ml contents>
end
, скомпилировать каждый, слинковать с pre.cmo + файлы по порядку + post.cmo,
запустить с нужными аргументами.
Для хранения и изменения схемы бд:
По окончании соответствующего migrate/rollback -- срать полученной схемой
в dbscheme.ml.
Как получать схему -- хранить соответствия "таблица -> мета * столбцы"
и изменять её функциями из миграций (add_column, change...).
Для использования бд -- модели.
Модель -- объект с нужными полями и методами, генерится по схеме.
Как связи обрабатывать? Как вариант -- разделить мета (из чего генерится
тип объекта) и код модели?
Но как тогда валидировать? В мета должна быть ссылка на функцию, которая
валидирует.
% model -- генерит по схеме таблицы __filename ^"s" класс
^^^^^^^ а вообще, это надо для всех моделей, поэтому в общем шаблоне будет.
let myvalfunc s = s <> ""
% validate_fun "fieldname" myvalfunc
-- прописывает нужное в метод model#validate || failwith "fieldname"
или типа-инлайн:
% begin_validate
check (self#x > 123)
% end_validate
->
print_string "let () = add_validation ("
print_string "#line fff, 123"
print_string "check (self#x > 123)"
print_string ");;"
% validate_presence "fieldname"
== "check (self#fieldname !~ /\s*/) || failwith "fieldname""
---
Связи между моделями -- как? Очевидно, список надо получать лениво
и/или мемоизировать (то есть, method subs = Lazy.force value_subs).
А как писать туда? Видимо, model#set_subs newsubs?
Для более годных сообщений об ошибках можно при стейджинге делать:
- линковать все pre-, смотреть на статус
- линковать все pre-, let mlt = [], post-, смотреть на статус
- пробовать парсить ошибки компилятора, ещё переименовать директивы в
dir_имя_StrLstHshBdy, и в случаях, когда не найдено, расшифровывать имя.
Или же более надёжно: если оказывается так, что компилируется с ошибкой,
брать все директивы, пихать их в let _ = dir_... let _ = dir_...,
писать это после всех pre-, можно даже "двоичным поиском" делить их
(сейчас один набор проверили, потом другой; вспомнить задачи про
нахождение отравленного сосуда), успешную компиляцию выкидывать,
неуспешную уточнять, пока не будут найдены отсутствующие шняги.
Хотя достаточно только одну найти -- первую. То есть, всё, что встретилось
по порядку в Dir, делим пополам, идём: 0..7=fail 0..3=fail 0..1=ok ->
2..3=fail -> проверяем 2, затем 3.
- если посмотреть на пункт "линковать все pre-", например, то там тоже может
быть нужно "половинное деление", чтобы знать, где лажа. Или даже лажа
может быть в тех вещах, которые мы добавляем вне pre-, перед ними.
Вывод такой: все файлы надо хранить в памяти (можно сразу с #line),
сделать кеш с явной очисткой нужных сущностей после выхода из Staging.stage,
но некоторые сущности прилепить намертво, типа src/codegen.ml или будущего
tpl/internal/dir_loc.ml. Там же: подумать, что ещё можно вынести в такое
кешируемое. mldir, call_generation? А может и "let _ = dir_..." тоже
вынести в такие сущности? Нехай себе компиляется, если всё успешно, как
есть, а при неудаче -- штатная бисекция по ним тоже.
- проблема при генерации миграций -- пользовательские функции вклеиваются
в результат напрямую, и там могут быть ошибки "забыли закрыть скобку",
"висящий let qwe =". В качестве простого решения -- натыкать после
пользовательского кода "(* CODE-END *). Если ошибка компиляции, то по
этим каментам обрезать исходник (от начала и до камента), пока не найдётся
такой камент, добавление которого вызывает ошибку. Её и возвращать
как конечную ошибку компиляции.
Как жить с шаблонами, моделями и базой данных.
В моделях имеются обращения к бд. При рендеринге шаблонов эти обращения
не нужны, так как монады и тонны запросов там, где можно килограммы.
Также есть шаблоны, рендерящие другие шаблоны.
В качестве апи -- шаблон принимает объект env с определёнными методами,
содержащими чистые данные.
Нужен анализ шаблона, такой, чтобы он определял какие значения нужны
шаблону и всем шаблонам, которые он может рендерить, чтобы можно было
подставить эти значения из модели, если это возможно сделать на основании
описания модели.
Синтаксически в шаблоне будут "@идентификатор<дальнейшее>".
Дальнейшее -- ".field"*. Раскрывать в "env#идентификатор<дальнейшее>".
Это нужно в основном для анализа шаблона (какие данные нужно предоставить),
но, с другой стороны, просто красиво.
(подумать, что будет, если в "высокоуровневом" будет env#.. -- оно будет
запрошено по типам, но надо будет предоставлять руками; а ещё?)
Order.{create,find,delete} -- создают инстансы модели
В контроллере будет open Render_to_string, поэтому надо предоставить
Order.Show.render ?bufsz env для рендеринга в строку.
В шаблоне, учитывая взаимную рекурсию, невозможно сгенерировать красивые
модули, поэтому будет тупо: order_show buf env для рендеринга в буфер.
При рендеринге необходимо проставлять руками всё, нужное в env, либо
передавать env = object method myshit = myshit end.
На высоком уровне --
@Controller.TemplateName.render locals: { local1 : val1 .. }
либо
@Controller.TemplateName.render env: { local1 : val1 .. }
это генерить в ((.. низкоуровневое ..) : возвращаемый тип) и проверять,
точно ли есть такой контроллер и такой шаблон.
Часть данных из требуемых шаблону можно брать из моделей и их связей,
насчёт остальных данных -- необходимо проверить, что программист их
предоставляет. Проверка -- по типу env.
Подумать, что делать с locals. По идее, нужна возможность переопределять их,
поэтому будет копирование объекта за исключением методов, переопределённых
через locals.
В контроллере надо уметь описывать методы env и способы их заполнения,
так, чтобы эти описания использовались при подготовке env для конкретного
шаблона. Это должно быть использовано вместе с получением данных из модели.
Чтобы было возможно подготавливать env полуавтоматически или автоматически,
высокоуровневый вызов рендеринга из шаблона должен быть выполнен в виде
директивы, которая развернётся в подготовку env и вызов рендеринга с этим env.
Опционально позволять указывать функцию env -> env, которая будет выполнена
до или после автоматики.
Как жить с миграциями.
Как должен выглядеть процесс использования миграций:
- был компилирующийся софт.
- добавили миграцию в proj/db/migrate/1111.mlt.
- добавили код, ссылающийся на изменённое/новое.
- dresina make:
- модели, ссылающиеся на изменённое/удалённое, должны давать ошибку
компиляции.
- как определять, что модели надо перегенерировать -- зависимость
от файла, хранящего схему.
- файл, хранящий схему, зависит от списка миграций и от каждой миграции.
- его нужно целиком перегенерировать при изменении миграций.
- dresina-server db:migrate:
- в dresina-server должны быть вкомпилированы все миграции.
- в бд должна быть таблица schema_migrations с ид применённых миграций.
- при старте сервер должен проверять последнюю миграцию в бд, сверять
с последней вкомпилированной миграцией, и отказываться работать,
если не совпадает.
(а если сервер отказывается работать, не следует ли ему автоматически
накатить нужные миграции? разве что продумать продакшон: бинарник
обновили и запустили, бд обновляем тоже, но между этими моментами есть
какое-то время, и худо-бедно пусть работает старое.)
- обработка моделей должна быть двухэтапной, учитывая возможную взаимную
зависимость моделей: 1. скинуть то, что каждая модель предоставляет
(таблица, атрибуты, выборки), 2. в остальных моделях использовать эти
знания для генерации кода.
С другой стороны, тогда не получится модульная компиляция, если взаимные
зависимости.
Да даже насчёт "зависимости от себя" непросто.
Можно вынести то, что надо снаружи (create, find) в let rec and rec ..,
окультурить модулями, а снаружи использовать это.
Но ещё проблемы: всякие after_create-хуки, которые должны быть в
Model.create, могут как-то ссылаться на то, что в нерекурсивной части модели.
Наверное, надо сделать "@Model.{attr,meth}" для разворачивания в реальное
обращение к другой модели, и "@{attr,meth}" для обращения к своим
атрибутам/методам.
Описания миграций:
- unique index и check руками -- подумать.
описания уникальности должны быть в миграциях. И там должно быть известно,
1. какое исключение должно быть кинуто, 2. какие его аргументы должны быть,
чтобы можно было обработать (например, отрисовать страницу).
Тем не менее, если будет желательно получить полный список ошибок, если
хоть одна обнаружена (например, "поле A пустое", что проверяется в модели),
возможно попробовать проверить также и уникальности, через тупую и в целом
не рабочую для этой цели выборку из бд.
Тут же подумать про "миграции постфактум" / "ручные миграции", которые
уже были проведены руками, реально изменили схему, и которые надо учесть
для работы с ними в моделях. Наверное, такие миграции должны быть
не-откатываемыми.
В целом: сделать неоткатываемые миграции.
Сделать так, чтобы в модели можно было ссылаться на глобальный контекст,
типа "user_id из запроса", чтобы можно было
"scope .. where user_id = @user_id".
А лучше -- если надо, пусть контроллер создаёт модель, параметризованную
аргументами (и её же в env передаёт).
Про "сервер промеж nginx и дрезиной" и миграции:
Когда юнипорн видит новый бинарник, он 1. новые соединения принимает,
но воркерам не даёт, 2. запускает новый бинарник, 3. как только новый
начинает слушать порт, шлёт запросы туда, и прибивает старый бинарник,
как тот доработает, 4. если новый бинарник падает с ошибкой при старте
(до принятия соединений), всё продолжает обрабатываться старыми воркерами.
При этом новый бинарник должен (*) сам попытаться применить все миграции,
которые ждут своей очереди, и, в случае ошибки, откатиться на ту миграцию,
которая была при его запуске.
(*) -- как вариант, он может подождать, пока на базе никого не будет,
чтобы ничего не сломать в обработке текущего запроса. Либо подождать
фиксированное время. Либо подождать сигнала от юнипорна, который скажет
"всё, старый воркер обработал все соединения". При этой схеме будет
задержка обработки, равная "время, пока старый воркер кончит" +
"время на применение миграции". Но юнипорн будет малацом, он придержит
эти запросы для нового воркера.