Простая модель данных


miketomlin
718

Вдобавок к недавним около программистским архитектурным топикам хочу предложить обсудить (оплевать :)) одну простую модель данных.

Суть в том, что данные для роутинга хранятся вместе с прочими данными в БД (не считая общей адресной маски, описывающей все допустимые адреса). При роутинге после попадания адреса под общую маску (или еще до этой проверки) из него выделяются параметры, прежде всего компоненты пути: /cat[/obj] (obj – это все компоненты после первого вместе с разделяющими их слешами). Затем по выделенным компонентам пути происходит выборка из одной или двух таблиц БД. В таблице первого уровня (таблице категорий, корневой таблице) хранятся слаги первого уровня для cat, в таблицах второго уровня (таблицах объектов) – слаги или числовые идентификаторы второго уровня для obj.

Значение cat идентифицирует не только категорию (запись корневой таблицы), но и соответствующую ей таблицу объектов (для того чтобы несколько категорий «смотрели» на одну таблицу объектов, обычно используются представления). Значение obj не обязательно идентифицирует объект в таблице (каждая категория имеет один из четырех режимов доступа к объектам, включая режим запрета на указание объекта в адресе; см. первоисточник).

Описанного обычно хватает для работы админки (соглашения по формату хранения контента в расчет не беру). Для морды сайта данные для роутинга обычно дополняются, например полем module для хранения имени/номера модуля/контроллера (именованные экшены обычно не используются, т.к. архитектура ориентирована на REST, но в принципе ничего не мешает хранить имя экшена вместе с именем контроллера в одном поле или в отдельном поле) или флагами разрешения GET-параметра, допустим, ?p= отдельно для адресов форматов /cat и /cat/obj.

Обсудить в первую очередь хочется такие моменты:

  • имеющиеся ограничения на формат адреса (на самом деле их нет) и т.п.;
  • производительность с учетом использования БД для роутинга (плиз, также не забывайте учитывать, что тут идет предвыборка и прочих полезных данных);
  • работа с БД во фронте, возможное деление контроллеров на два вида (желающих унаследовать соединение с БД от фронта и нет) и т.п.


Sly32

А что тут обсуждать/оплевывать? Обычная, часто встречающаяся модель с вариациями. Есть маска урла, по ней идет обращение в БД, а уж как там искать — слаги, айди- без разницы


miketomlin

Sly32, маска одна общая, предназначенная скорее для того, чтобы просто сузить границы дозволенного (исключить кириллицу, верхний регистр, левые GET-параметры и т.п. при обработке запросов). Реальный роутинг происходит уже на основе данных из БД.

P.S. Конечно, можно использовать и др. роуты в обычном понимании, но смысл в том, чтобы этого не делать. Чтобы адреса определялись логической структурой БД, например адрес /articles/my-first-article означает, что должна быть категория (точнее коллекция, т.к. через нее должен быть разрешен доступ к объектам) articles в корневой таблице и статья my-first-article в таблице site_articles (вместо префикса site_ может быть и др., но articles в имени таблицы определяется слагом категории).

———- Добавлено 21.03.2020 в 19:33 ———-

P.P.S. В принципе, как я писал в стартовом посте, модель позволяет, чтобы my-first-article было чем угодно, если установить для категории более мягкий в этом отношении режим.


danforth

miketomlin, какую задачу вы решаете? Для чего это все?


SocFishing

Встречал как-то я систему, в ней значит совсем все было в mysql. Шаблоны, какие-то непонятные вставки на PHP, ну и само собой роутинг и контент.

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

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

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

Осуждаю! «работа с БД во фронте» не догоняю что вы хотите делать, но еще раз, лучше придерживаться V — шаблоны и только отображение, M — основные функции, все что там у вас выполняется, зависимости и прочее, работа с базой данных, C — прослойка между MV где есть логическая часть Route, прием данных и передача результатов.

Не знаю, что вы хотели тут услышать. Рекомендую Laravel 7, я его обожаю, это просто. Если конечно PHP. Там же роутинг посмотрите, я так понимаю вашу логику можно с коробки сделать.


timo-71

miketomlin:
маска одна общая, предназначенная скорее для того, чтобы просто сузить границы дозволенного (исключить кириллицу, верхний регистр, левые GET-параметры и т.п. при обработке запросов). Реальный роутинг происходит уже на основе данных из БД.

Не претендую, но. У меня вообще никакой маски нет. Правила простые. Сначала предопределенные роуты.

'routes'  => [
    'index' => [
        'controller'    => 'Cms\\Controller\\SiteIndex',

'content-type' => 'text/html',
'allowed' => 'GET',
'tpl' => [ 'type' => 'twig',
'area' => _TWIGTPL .'/default.tpl',
'template' => '/main.twig'
],
],
'search' => [ 'controller' => 'Cms\\Controller\\Search',
'content-type' => 'application/json',
'allowed' => 'GET,POST',
'nologin' => 1
],
'usr' => [ 'controller' => 'Cms\\Controller\\Usr',
'content-type' => 'text/html',
'tpl' => [ 'type' => 'twig',
'area' => _TWIGTPL .'/default.tpl',
'template' => '/usr.twig'
],
'logined' => true,
],

Казалось бы, достаточно на все случаи, если есть программер под рукой. Но, гибкости хотелось, и меньше зависеть от админов сайтов. Поэтому:

'default_route' => [
    'controller'    => 'Fwe\\Controllers\\Dflt',

'content-type' => 'text/html',
'allowed' => 'GET',
'tpl' => [ 'type' => 'twig',
'area' => _TWIGTPL .'/default.tpl',
'template' => '/page.twig'
],
],

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

^[0-9a-z\_\-]{1,127}\.?[a-z0-9]{0,6}$

иначе исключение.

Примерно такие таблички (DВ данном случает Sqlite, но не важно)

CREATE TABLE "map" (

"_id" TEXT NOT NULL UNIQUE,
"section" TEXT,
"link" TEXT UNIQUE,
"name" TEXT NOT NULL,
"parent" TEXT DEFAULT '',
"is_view" INTEGER DEFAULT 0,
"is_hit" INTEGER DEFAULT 0,
"order" INTEGER DEFAULT 10000,
"img" TEXT DEFAULT '',
"colls" TEXT DEFAULT '',
"plug" TEXT,
"up" INTEGER DEFAULT 0,
PRIMARY KEY("_id")
)
CREATE INDEX "section_link_name" ON "map" (
"section",
"link",
"name"
)

В общем то, такой подход позволяет обработать любой динамический урл заданный админом сайта в админке. Таблица map это просто карта разделов сайта, каждый из которых может иметь набор статей и коллекций colls (категория каталога товаров или др. объект). Иными словами, по урлу /articles/my-first-article, в табличке мап будет только articles, а my-first-article попытаемся найти в другой, при условии, что articles в мап есть. Аналогично с коллекциями. Это накладывает некоторые лишние действия при изменении урла в мап, но все решаемо. Профит, главным образом в том, что админ сайта не парит мне мозг, когда ему надо какой то свой урл сделать вместо /catalog/samosvaly/kamaz-65115, а легко делает /avto/gruzovye/samosval/kamaz-65115. Ну или просто /kamaz-65115, если коллекция самосвалы приписана к морде.

miketomlin:
производительность с учетом использования БД

Не поленюсь, несколько строк на коленке накидать:

$sql="SELECT count("_id") FROM "map";";

prnt(Core::dbsq('site.db')->q($sql, null)->R(),1);
prnt(Core::dbsq('site.db')->rows(),1);
prnt(Core::dbsq('site.db')->q(
'SELECT * FROM "map" WHERE "section"=:section ;', [ [':section'=>'company'],
[':section'=>'faq']
])->R(),1);
prnt(Core::dbsq('site.db')->rows()); die;

И выложить результат.

Array

(
[sql] => SELECT count("_id") FROM "map";
[sqltype] => select
[sqlnum] => 3
[rows] => 1
[changes] => 0
[allrows] => 1
[eCode] => 0
[error] =>
[time] => 0.12(ms)
[affected_rows] => 0
)
Array
(
[0] => Array
(
[count("_id")] => 363
)
)
Array
(
[sql] => SELECT * FROM "map" WHERE "section"=:section ;
[sqltype] => select
[sqlnum] => 4
[rows] => 12
[changes] => 0
[allrows] => 12
[transaction] => 1
[eCode] => 0
[error] =>
[time] => 0.24(ms)
[affected_rows] => 0
)

Таких разделов сайта не будет слишком много, в данном случае 363 и за 0.24(ms) легко получить 12 разделов 1 секцией имеющих

 

[':section'=>'company'],
[':section'=>'faq']

Т.е. имеем все доступные документы /company/about, /company/contacts, /company/…. Если среди них есть, с текущим урлом, то продолжаем дальше, если нет 404. Всякие бредкрумбы уже готовы.


Sly32

А у меня так

url.py


...
url(r'^katalog-tovarov/(?P<category>[-\w]+)/(?P<slug>[-\w]+)/$', views.ProductDetailView.as_view(),
name="product_detail"),
...

вьюха-контроллер по вашему


...
class ProductDetailView(TemplateView):
template_name="core/products/catalog_detail.html"

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
...

модели


class Page(BaseSeo):
TYPE = (
('page', 'Страница'),
('item, 'товар')
)
post_type = models.CharField(
'Тип записи', help_text="Страница, новость или канал", max_length=20, choices=TYPE, default=TYPE[0][0]
)
created_at = models.DateTimeField('Добавлен - created at', auto_now_add=True)
last_modified = models.DateTimeField(auto_now=True)
title = models.CharField('Название', max_length=255, blank=True)
slug = models.SlugField(max_length=255, blank=True, unique=True)
content = models.TextField('Содержимое', blank=True)
main_category = models.ForeignKey(
'Category', blank=True, on_delete=models.SET_NULL, null=True, related_name="page_category"
)
category = models.ManyToManyField('Category', blank=True, related_name="page_main_category")
thumbnail = models.ImageField('Микроминиатюра', upload_to='thumbs', max_length=255, blank=Tru

...
class Category(BaseSeo):
title = models.CharField('Категория', max_length=255, blank=True)
slug = models.CharField('ЧПУ', max_length=255, unique=True, blank=True)
parent = models.ForeignKey(
'self',
on_delete=models.CASCADE,
verbose_name="Родительская категория",
related_name="parent_category",
blank=True,
null=True
)
order = models.IntegerField('Порядок в меню', null=True, default=1)
thumb = models.ImageField('Логотип', upload_to='thumbs', max_length=255, blank=True)
description = models.TextField('Описание категории', blank=True)

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


timo-71

Sly32:
А у меня так

Вы с точки зрения программера. Попробуйте, чтобы без обращения к вам, чтобы часть категорий была доступна вместо katalog-tovarov/tablet/… по адресу samsung/tablet/…, другая tablet/cases/…, при том, что в любой момент может появится еще-какой-нибудь/tablet после обновления каталога. Или будет заведена новая категория аксессуаров, где нет брендов. Потом, если получится, посчитайте +/-

Единственно, что напрашивается, если клиента побреете, что сначала бренд и ни как иначе.

url(r'^(?P<vendor>[-\w]+)

Что само по себе, напрягает

А если, еще и статья нужна с урлом /samsung/почему-самсунг-лучше-айфона, а по урлу /samsung/полезное, листинг анонсов статей, которых 100500.

Поэтому, считаю, такого плана роутинг не лучшее решение, тут и программер скажет, ну его, анриал

—-

Иными словами — изменения в базе, легко меняют любой урл, начиная с первой секции.


Sly32

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


miketomlin

danforth, тут по-моему все очевидно. См. метки темы 😉

SocFishing, немного не в тему. Речь не о том, чтобы все перетянуть в БД (это идиотизм, согласен), а только о том, чтобы перетянуть в БД данные для роутинга (причем не тупо в виде обычных роутов). Чтобы БД стала более самодостаточной, т.е. чтобы в ней хранился не только контент (не считая файлов, относящихся к контенту), но и полностью определялись адреса, по крайней мере простые, соответствующие этому контенту.

timo-71, маска не входит в модель. Но она предполагается. Так обычно работают приложения, чтобы не пытаться «лезть в БД» вообще без каких-либо ограничивающих условий, например я могу в конце маски прописать подмаску для строки параметров (\\?p=[1-9]\\d{0,9})?, чтобы не пытаться «лезть в БД» при наличии левых GET-параметров или левого значения допустимого параметра.

Смысл в том, чтобы не использовать «сначала предопределенные роуты», а перенести и их в БД, т.е. «префиксные» слаги, имена контроллеров (последние легко соотносятся с типами объектов в этих коллекциях, поэтому нет ощущения, что мы вносим в БД какие-то левые имена) и т.п. для коллекций разместить в корневой таблице вместе с собственными данными коллекций, например их названиями («Пользователи» и т.п.).

timo-71:
В общем то, такой подход позволяет обработать любой динамический урл заданный админом сайта в админке.

Да, верно. Только в наших админках обычно запрещен прямой доступ к корневой таблице, чтобы неквалифицированный «админ» не наворотил дел. А если нужно организовать доступ к категориям (записям корневой таблицы), не являющимся коллекциями, то просто создаются представление корневой таблицы, скрывающее служебные поля и записи, и соотв. коллекция для него (см. концовку статьи по ссылке в стартовом посте).

В любом случае это все хранится в БД/решается на уровне БД. Мы не лезем в код админки, а тем более морды сайта, про которую мы вообще можем ничего не знать. Админки или морды может вообще не быть в данный конкретный момент!!!

P.S. Но все же модель не включает описание построения произвольных таксономий (с любым уровнем вложенности и т.п.), она только описывает иерархию «коллекция-объект (элемент коллекции)» и возможность расширения.

———- Добавлено 22.03.2020 в 17:07 ———-

timo-71:
Если среди них есть, с текущим урлом, то продолжаем дальше, если нет 404. Всякие бредкрумбы уже готовы.

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


miketomlin

P.S. Предвыборка данных обновляемого объекта полезна для того, чтобы обновлять в БД не все подряд поля, а только необходимые.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *