«phpClub» — архив тем ("тредов"), посвящённых изучению PHP и веб-технологий.
Аноним 2019/04/03 03:06:29  №1374075 1
>>1353705 (OP)
Вопрос по HTML и CSS...

Задача: сделать вывод текста и текстареи - в две колонки,
как тут: https://css-live.ru/articles/css-gridy-css-kolonki-%E2%99%A5.html
но так, чтобы при изменении размера текстареи, изменялся и размер line-height,
и чтоб не ровно на половину страницы разделялось, а с небольшим отступом.

Вопрос - как правильно сделать?
Ответы: >>1377517
Аноним 2019/04/09 02:09:30  №1377517 2
threecolumns.png (46, 1309x605)
605x1309
>>1374075
Решил при помощи таблицы. Пикрил.
Ответы: >>1378171
Аноним 2019/04/10 05:39:02  №1378171 3
>>1377739

Жаль, что-то у меня руки не дошли проверить код. Но я бы тебе посоветовал:

- использовать объекты вместо массивов для ключевых типов данных, которые используются во многих функциях в коде
- не городить такие массивы, когда можно обойтись без них. Тут вместо сложного массива principal можно было использовать внутри функции просто переменные вроде error, role, и при необходимости перед возвратом результата собирать из них массив. Хотя, тут можно просто возвращать кортеж (role, error), как мне кажется.
- разбивать стену кода на отдельные функции. Ты и токены проверяешь, и к БД подсоединяешься, и что только не делаешь.
- в try/except указывать конкрентные классы исключений, которые тебя интересуют, а не ловить все подряд

>>1377564

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

>>1377517

Чтобы не нарушать семантику, можно еще использовать display: table вместо настоящих таблиц. А так, да, вертикальное центрирование - это table или flexbox.

>>1377063

Sublime Text, Netbeans for PHP, Eclipse PDT, если ты богат (или если можешь выпросить лицензию как студент) и у тебя быстрый проц, то PHPStorm. VS Code неплоха, но не у всех быстро работает.
Ответы: >>1378381
someApprentice 2019/04/10 14:02:33  №1378381 4
image.png (233, 1920x1080)
1080x1920
Протестировал WAMP и сейчас собираюсь писать сервис сообщений. И сначала я хотел бы обсудить архитектуру которую я выбрал. Я считаю её безупречный, но я не безупречный разработчик, поэтому мне хотелось бы услышать опытный взгляд со стороны.

Задача
Каждый пользователь может написать другому пользователю. При первой отправке сообщения создаётся диалог. Сообщения могут быть текстовые, могут быть голосовые и могут быть видео. Каждое сообщение может удаляться либо у себя лично, либо у обоих пользователей. Диалоги так же могут удаляться у каждого пользователя лично (но не у обоих, т.е. каждый пользователь имеет только ссылку на диалог).

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

Диалоги могут быть приватные (тет-а-тет) а могут быть публичные (конференции).


Соответственно архитектура будет такая:

пользователь:
- ...

текстовое_сообщение:
-uuid
-пользователь (автор сообщения)
-сообщение
-дата
-прочитано (булевый)
-...

аудио_сообщение:
-uuid
-пользователь (автор сообщения)
-звукозапись (путь/к/аудиофайлу)
-дата
-прочитано (булевый)
-прослушано (булевый)
-...

видео_сообщение:
-uuid
-пользователь (автор сообщения)
-видеозапись (путь/к/видеофайлу)
-дата
-прочитано (булевый)
-просмотрено (булевый)
-...


текстовое_сообщения: (хранит в себя ссылки на сообщения для каждого отдельного пользователя)
-uuid
-пользователь (кому принадлежит ссылка)
-диалог (диалог в котором сообщение находится)
-сообщение
-...

аудио_сообщения:
-...

видео_сообщения:
-...


прикрепление_изображения:
-uuid
-сообщение
-изображение
-...

прикрепление_аудиозаписи:
-...

и т.д. за исключением что у каждого прикрепления своя ссылка на источник.


диалог:
-uuid
-приватный (булевый)
-...

участники: (для приватных диалогов всегда будет два участника)
-uuid
-диалог
-пользователь
...

диалоги: (ссылки на диалоги для каждого пользователя)
-uuid
-пользователь
-диалог
...


Такова вся архитектура. База данных будет psql.

Название таблиц в боевой базе данных будет идентично т.е. будет и сообщение и сообщения. Это же не создаёт затруднения для понимания?

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


>>1378171
>Жаль, что-то у меня руки не дошли проверить код. Но я бы тебе посоветовал:
>
>- использовать объекты вместо массивов для ключевых типов данных, которые используются во многих функциях в коде
>- не городить такие массивы, когда можно обойтись без них. Тут вместо сложного массива principal можно было использовать внутри функции просто переменные вроде error, role, и при необходимости перед возвратом результата собирать из них массив. Хотя, тут можно просто возвращать кортеж (role, error), как мне кажется.
>- разбивать стену кода на отдельные функции. Ты и токены проверяешь, и к БД подсоединяешься, и что только не делаешь.
>- в try/except указывать конкрентные классы исключений, которые тебя интересуют, а не ловить все подряд

>Жаль, что-то у меня руки не дошли проверить код.
Всё хорошо, у меня было время чем другим заняться. Я рад, что сэкономил ваше время.

>- использовать объекты вместо массивов для ключевых типов данных, которые используются во многих функциях в коде
>объекты
Я правильно понимаю, что в Питоне объект, это instance класса, как в PHP, а не как в JS? Для каких ключевых типов данных вы имеете ввиду?


>- не городить такие массивы, когда можно обойтись без них. Тут вместо сложного массива principal можно было использовать внутри функции просто переменные вроде error, role, и при необходимости перед возвратом результата собирать из них массив. Хотя, тут можно просто возвращать кортеж (role, error), как мне кажется.

>Тут вместо сложного массива principal можно было использовать внутри функции просто переменные вроде error, role, и при необходимости перед возвратом результата собирать из них массив.
Разве это не создаст дополнительные проверки и не раздует код? Т.е. во время ответа писать: if error is not None: principals['extra']['error'] = error; Почему нельзя собирать массив "на ходу"?

>Хотя, тут можно просто возвращать кортеж (role, error), как мне кажется.
Возвращать кортеж из функции авторизации, вы это имели ввиду? Это не правильно, потому что principals сериализуется в json и возвращается клиенту.


>- разбивать стену кода на отдельные функции. Ты и токены проверяешь, и к БД подсоединяешься, и что только не делаешь.
Конечно. Это только черновик где я тестирую чтобы посмотреть как работает код.

У меня будет вопрос по архитектуре, когда я его сформулирую.


>- в try/except указывать конкрентные классы исключений, которые тебя интересуют, а не ловить все подряд
Понял.
Ответы: >>1380460
Аноним 2019/04/13 18:20:25  №1380460 5
>>1378381

Использование таблиц с похожими именами будет однозначно вызывать путаницу. Более того, я пока сам не понял, в чем разница между "сообщением" и "сообщениями" или "диалог" и "диалоги".

Если ты хочешь делать для каждого пользователя свою копию сообщения, то не проще ли просто сделать таблицу "диалог", и таблицу "сообщения", где в сообщении стоит ссылка на диалог и ссылка на получателя, кому оно предназначено? То есть, так:

диалог:
- uuid

участники диалога:
- id пользователя
- id диалога

сообщения: (копия для каждого получателя)
- uuid
- id, объединяющий копии одного сообщения
- id диалога
- id отправителя
- id получателя этой копии
- признак прочтения
- текст

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

Далее, у тебя есть наследование таблиц (для текстовых, аудио, видео сообщений сделаны отдельные таблицы). Ты использовал Concrete Table Inheritance. Не очень удобно делать их разными таблицами. Представь, что тебе надо получить последние 10 сообщений в диалоге. Тебе надо будет запрашивать данные из каждой таблицы (из текстовых сообщений, аудио сообщений, видео сообщений) и объединять. Как ты себе представляешь эффективную пагинацию по ним?

Логичнее было бы использовать другой паттерн наследования, например, Single Table Inheritance или Class Table Inheritance. В случае STI мы делаем единую таблицу для всех видов сообщений. В случае CTI мы делаем одну таблицу для полей, которые есть в каждом виде сообщения (отправитель, время отправки, получено), и отдельные таблицы для доп. полей (которые есть только у опр. вида сообщения):

- http://design-pattern.ru/patterns/class-table-inheritance.html
- http://design-pattern.ru/patterns/single-table-inheritance.html

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

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

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

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

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

Поэтому, думаю, схему надо доработать.

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

Но оно не дает нам эффективно делать пагинацию по сообщениям, что сводит плюсы на нет.

> Я правильно понимаю, что в Питоне объект, это instance класса, как в PHP, а не как в JS? Для каких ключевых типов данных вы имеете ввиду?

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

> Разве это не создаст дополнительные проверки и не раздует код? Т.е. во время ответа писать: if error is not None: principals['extra']['error'] = error; Почему нельзя собирать массив "на ходу"?

Потому, что это разные задачи: получение данных и формирование ответа на JSON-запрос. И скорее всего, лучше чтобы отдельно была функция проверки авторизации, а отдельно функция, которая формирует JSON-ответ. А не совмещать это все в одной функции. Но так как это тестовый код, то это не принципиально.
Ответы: >>1380677 >>1380677
someApprentice 2019/04/14 08:05:28  №1380677 6
>>1380460
>Использование таблиц с похожими именами будет однозначно вызывать путаницу. Более того, я пока сам не понял, в чем разница между "сообщением" и "сообщениями" или "диалог" и "диалоги".
Сообщение - это конкретная запись сообщения имеющая все данные о нём, от содержания до автора, id, даты публикации и т.д., а сообщения - это запись какие сообщения принадлежат какому пользователю и/или диалогу. Т.е. "сообщения" хранит ссылки для пользователя на id конкретного сообщения.

Как можно понятней назвать таблицы чтобы не вызвать путаницу?

Такая схема была предложена вами при обсуждении моей задачи чата на чистом JS:

https://phpclub.tech/pr/res/1019301.html#1028550
>чаты и диалоги лучше делать одинаковым способом, то есть представить диалог 2 пользователей как чат из 2 участников.

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

Мне хочется казать, что я слегка её отшлифовал, убрав многократное копирование сообщений заменив его "ссылками" на сообщения.


>Если ты хочешь делать для каждого пользователя свою копию сообщения, то не проще ли просто сделать таблицу "диалог", и таблицу "сообщения", где в сообщении стоит ссылка на диалог и ссылка на получателя, кому оно предназначено? То есть, так:
>
>диалог:
>- uuid
>
>участники диалога:
>- id пользователя
>- id диалога
>
>сообщения: (копия для каждого получателя)
>- uuid
>- id, объединяющий копии одного сообщения
>- id диалога
>- id отправителя
>- id получателя этой копии
>- признак прочтения
>- текст
>
>Кстати, в больших чатах невыгодно будет делать для каждого свою копию сообщения. Выгоднее делать всего одну копию для всех.
>- id, объединяющий копии одного сообщения
Это получается что объединяющее копия одного сообщения это отдельная сущность/таблица? Я не понимаю тогда, это получается тоже самое что и я предложил, только колонки в разных таблицах. Ну и контент сообщения в моём предложении находиться только оригинальном, а не дублируется.

Повторюсь, что в ссылке на сообщение находиться id на сообщение, а не его содержимое:

>текстовое_сообщения: (хранит в себя ссылки на сообщения для каждого отдельного пользователя)
>-uuid
>-пользователь (кому принадлежит ссылка)
>-диалог (диалог в котором сообщение находится)
>-uuid сообщения
>-...


>Далее, у тебя есть наследование таблиц (для текстовых, аудио, видео сообщений сделаны отдельные таблицы). Ты использовал Concrete Table Inheritance. Не очень удобно делать их разными таблицами. Представь, что тебе надо получить последние 10 сообщений в диалоге. Тебе надо будет запрашивать данные из каждой таблицы (из текстовых сообщений, аудио сообщений, видео сообщений) и объединять. Как ты себе представляешь эффективную пагинацию по ним?
>
>Логичнее было бы использовать другой паттерн наследования, например, Single Table Inheritance или Class Table Inheritance. В случае STI мы делаем единую таблицу для всех видов сообщений. В случае CTI мы делаем одну таблицу для полей, которые есть в каждом виде сообщения (отправитель, время отправки, получено), и отдельные таблицы для доп. полей (которые есть только у опр. вида сообщения):
>
>- http://design-pattern.ru/patterns/class-table-inheritance.html
>- http://design-pattern.ru/patterns/single-table-inheritance.html
>
>В обоих случаях мы можем легко получить последние N сообщений или сделать пагинацию без использования OFFSET.
Я думал об этом и предполагал, что у psql есть встроенная поддержка этого, и как оказалось, действительно есть https://postgrespro.ru/docs/postgresql/11/ddl-inherit


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


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


>>1380460
>> Я правильно понимаю, что в Питоне объект, это instance класса, как в PHP, а не как в JS? Для каких ключевых типов данных вы имеете ввиду?
>
>Да, как в PHP. Я имел в виду, что если у тебя в коде есть какой-то массив, который много где используется, передается и возвращается разными функциями, то правильнее будет сделать его объектом. Но, как я понял, там был просто тестовый код, и тогда это не важно.
Этот массив был бы объектом с одним свойством этого самого массива?
someApprentice 2019/04/14 08:07:19  №1380679 7
Надеюсь мой ответ не потеряется >>1380677
someApprentice 2019/04/15 13:29:51  №1381835 8
>>1380677
>>> Я правильно понимаю, что в Питоне объект, это instance класса, как в PHP, а не как в JS? Для каких ключевых типов данных вы имеете ввиду?
>>
>>Да, как в PHP. Я имел в виду, что если у тебя в коде есть какой-то массив, который много где используется, передается и возвращается разными функциями, то правильнее будет сделать его объектом. Но, как я понял, там был просто тестовый код, и тогда это не важно.
>Этот массив был бы объектом с одним свойством этого самого массива?
Или я подумал, что это может быть объект со свойствами которые содержатся в этом массиве, и сделать этому объекту метод toArray().
someApprentice 2019/04/19 07:29:51  №1383892 9
>>1380677
>Как можно понятней назвать таблицы чтобы не вызвать путаницу?
Также, можно добавлять комментарии к таблицам. Разработчики часто их читают?

https://postgrespro.ru/docs/postgrespro/11/sql-comment
Ответы: >>1387125 >>1388215 >>1388214
someApprentice 2019/04/23 04:34:52  №1387125 10
>>1380677
>>1383892
>>Использование таблиц с похожими именами будет однозначно вызывать путаницу. Более того, я пока сам не понял, в чем разница между "сообщением" и "сообщениями" или "диалог" и "диалоги".
>Сообщение - это конкретная запись сообщения имеющая все данные о нём, от содержания до автора, id, даты публикации и т.д., а сообщения - это запись какие сообщения принадлежат какому пользователю и/или диалогу. Т.е. "сообщения" хранит ссылки для пользователя на id конкретного сообщения.
>
>Как можно понятней назвать таблицы чтобы не вызвать путаницу?
Можно вместо создания бесконечного количества ссылок создать поле с типом ARRAY и помещать в него все ссылки (UUID) на необходимые данные.

https://postgrespro.ru/docs/postgresql/11/arrays
https://postgrespro.ru/docs/postgresql/11/functions-array

Соответственно, все операции будет выполнятся легко и ловко.

Прим.

Message:
-uuid
...

Dialog:
-uuid
-private BOOLEAN
-participants UUID[] //массив из uuid получателей

Participant:
-uuid
-user
-messages UUID[] //массив из uuid сообщений

И все операции выполняются очень легко:

// Пользователь удаляет свою копию сообщения
UPDATE Partcipant SET messages = array_remove(messages, uuid_of_message) WHERE uuid = uuid_of_participant;

// Проверить существует ли приватный диалог,
// не смотря на то что один из пользователей когда-то покинул диалог
SELECT * FROM Dialog WHERE private = true AND (uuid_of_alice = ANY(participants) OR uuid_of_bob = ANY(participants));
Ответы: >>1388216
Аноним 2019/04/25 02:21:07  №1388212 11
>>1380677

> Сообщение - это конкретная запись сообщения имеющая все данные о нём, от содержания до автора, id, даты публикации и т.д., а сообщения - это запись какие сообщения принадлежат какому пользователю и/или диалогу. Т.е. "сообщения" хранит ссылки для пользователя на id конкретного сообщения.

Это лучше назвать "message_to_user" или "user_message", как-то так. Тут https://www.sqlstyle.guide/ru/ советуют придумывать оригинальные имена, но в данном случае это трудно.

Твои названия сбили меня с толку, и скорее всего, собьют и других людей.

> Такая схема была предложена вами при обсуждении моей задачи чата на чистом JS:

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

И еще, у тебя в таблице "текстовое_сообщения" есть ссылка на диалог. Но ведь сообщение постится в один чат. Значит, у тебя для чата у каждого пользователя есть своя "копия" диалога со своим id? Вот такие вещи стоило бы пояснить заранее, увы, я этого не понял сразу.

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

При таком подходе есть проблема - в огромных чатах при отправке накладно получается делать 1000 копий сообщения, и в плане загрузки CPU, и в плане хранения на сервере. Для таких случаев можно попробовать такую идею: есть создатель чата, он создает "ключ чата" и сохраняет на сервер (или же сервер сам его генерирует). Каждый подключающийся к чату получает этот ключ и может шифровать и расшифровывать сообщения. Минус - когда один ключ есть у кучи людей, это не очень-то надежно. Но если это публичный чат, то и так любой может подключиться к нему и читать его. Чтобы отключившиеся от чата не могли больше читать его, надо периодически обновлять ключ чата. Те, кто отключился, не получат новую версию ключа.

В общем, попробуй применить инженерный подход: сравнение разных схем, определение плюсов и минусов, проверка типовых сценариев (переписка между пользователями, между несколькими, большой чат, правка и удаление сообщений, бан пользователей, выборка последних N сообщений, выборка списка диалогов с числом непрочтенных сообщений). Я, увы, с ходу не могу предсказать, что тут лучше подойдет. Но могу прокомментировать и покритиковать предложенную схему.

> Я думал об этом и предполагал, что у psql есть встроенная поддержка этого, и как оказалось, действительно есть

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

> Этот массив был бы объектом с одним свойством этого самого массива?

Зависит от ситуации, скорее всего объектом с полями. Ну например, у нас есть фильтр по товарам, который мы передаем в функции и получаем из функций, преобразуем. Логично делать его не массивом, а нормальным объектом с полями: мин. цена, макс. цена, категории, цвета, бренды, поиск по названию и тд. Я имел в виду, что в такой ситуации выгоднее использовать объект с методами, проверкой типов, чем просто непонятный массив, в котором непонятно даже какие поля есть.
Ответы: >>1388352
Аноним 2019/04/25 02:22:00  №1388214 12
>>1382020

> Делаю грамматического нациста, хочу сделать проверку по отсутствию знака препинания.

Проще всего искать так:

- в начале идет любая буква
- за ней пробелы
- за ней слово "а" или "но"
- за ним граница слова

>>1383863

Возможности очень хорошие, есть куча плагинов, но жрет память и проц. Если твое железо потянет, то неплохой вариант.

>>1383892

Я взял привычку всегда их ставить, и к таблицам, и к полям. Они отображаются во всяких визуальных программах для работы с БД вроде phpmyadmin.
Аноним 2019/04/25 02:26:21  №1388215 13
Аноним 2019/04/25 02:31:02  №1388216 14
>>1387125

> Можно вместо создания бесконечного количества ссылок создать поле с типом ARRAY и помещать в него все ссылки (UUID) на необходимые данные.

Можно, но это будет денормализация. Допустимо, но лучше сначала спроектировать нормализованную схему, а только потом денормализовать. И что там с внешними ключами?

> Participant:
> -messages UUID[] //массив из uuid сообщений

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

>>1388116

Я не вижу особой разницы. Возможно, нужен не абстрактный, а более реальный пример кода, тогда будет понятнее.

Во втором варианте можно считать результат, никуда его не сохраняя, в отличие от первого.
Аноним 2019/04/25 02:33:14  №1388217 15
someApprentice 2019/04/25 08:52:57  №1388352 16
Ответы: >>1388928
Аноним 2019/04/26 02:45:40  №1388928 17
>>1388352

Упс, теперь и я не могу так просто постить этот текст: https://pastebin.com/J8ePkQCK

Подозреваю, это из-за слова на буквы "к", "о", "н", "ф".
Ответы: >>1389946
someApprentice 2019/04/27 14:32:23  №1389946 18
Ответы: >>1394587
Аноним 2019/05/05 15:43:23  №1394587 19
>>1389946

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

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

SELECT FROM Participant WHERE user = ? AND partner = ? AND private =1

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

> Можно я оптимизацию БД оставлю на последнее?

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

Более того, часто под мессенджеры даже пишутся специализированные хранилища.

Насчет reply - а недостаточно тут просто сделать поле в сообщении "replyToUuid" со ссылкой на исходное сообщение? Без вложений.

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

Также, в таблицах вроде Conference_Reference можно не вводить отдельный uuid, а использовать например составной ключ (uuid конференции + id юзера). Возможно, так будет проще. А может и нет.

>>1389824

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

>>1389802

По идее в VSCode умное дополнение есть, оно называется intellisense или как-то так и его надо ставить отдельно для каждого языка.

>>1389598

> Я думаю, что пока подходы к авторизации методов WAMP не изучены, следует писать сырой код на if'ах, а подход с роутингом взять на заметку и держать в уме. А как думаете вы?

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

Возможно, придется использовать роутер и в нем проверять переданный клиентом токен. Или вместо id пользователя в идентификаторах каналов использовать трудноподбираемый токен (new-msgs-for-112345678765432) - тогда может быть, авторизация для подписки не потребуется.

Ответы: >>1397648
someApprentice 2019/05/11 13:13:34  №1397648 20
image.png (249, 1920x1080)
1080x1920
image.png (276, 1920x1080)
1080x1920
image.png (356, 1920x1080)
1080x1920
image.png (290, 1920x1080)
1080x1920
>>1394573
>По поводу импортов: я не очень понимаю, зачем ты в model/message.py импортируешь наследников. Обычно импортируют только то, что нужно в данном файле. И обычно наследник импортирует предка, а не наоборот. Потому я думаю, эти импорты надо просто убрать:
>
># models/message.py
>import text_message
>import voice_message
>
>Зачем они добавлены? Странно, что у тебя предок зависит от своих наследников (что они ему нужны).
Я понял свою ошибку. Я когда пытался получить все сообщения я использовал команду вида session.query(Message).join(Message_Reference).filter(Message_Reference.user == self.user).all() и получал только сущности родительского класса Message, и я подумал что родителю необходимо знать о детях чтобы получить их всех.
Я теперь понял, что импорты должны выполнятся там где выполняется этот запрос - это работает.

>Также, ты похоже выбрал Concrete Table Inheritance, возможно, что запросы к ней потребуют лишних UNION, судя по мануалу: https://docs.sqlalchemy.org/en/13/orm/inheritance.html#concrete-table-inheritance
К сожалению, мне не удалось найти какой паттерн наследования использует psql. Я косвенно предположил, что это именно он.pic-1

Использование UNION приводит к нагрузке?

Вообще, ORM не очень дружат с наследованием, как я могу посудить. Отношения между сущностями тяжело совершить с помощью встроенных инструментов и приходится делать метод для совершения запроса в ручную.pic-2 При получении сущностей выдается массив из сначала сущностей родителя, затем сущности ребёнка.pic-3 Я не знаю должно ли быть такое поведение. Хочется самому отсортировать только сущности детей и возвращать этот массив.


>> Следует сделать скрипт, который будет принимать все credentials, выполнять всю установку, а на выход выдавать все хэши и токены. Возможно генерировать сразу .env файл.
>
>Я думаю, достаточно только сгенерировать токены. В .env может быть еще куча других параметров. Но можно, конечно, выдавать заготовку .env файла.
>
>> Как обычно делаются такие файлы? Через Докер (не разу с ним не знакомился)?
>
>В dev среде можно использовать docker-compose для оркестрации докеров с отдельными приложениями. Докер, как правило, используется чтобы упаковать в образ программу с нужными ей библиотеками, например, определенную версию Питона или Ноды, чтобы ее не надо было устанавливать в систему руками. Код твоего приложения в докер-образ не кладется, а подмонтируется в него как внешний раздел. docker-compose заниамется тем, что просто запускает несколько докеров (например: микросервис авторизации и основное приложение). На Винде и Маке Докер запускает код в виртуальной машине с линуксом, а файлы прокидываются через сетевую файловую систему со всеми вытекающими.
>
>Ты можешь найти готовый пример приложения на PHP + nginx + mysql в докере и разберешься, я думаю.
Я ошибся когда писал, что у разных ролей wamp'а разные uri. Сейчас я сделал авторизацию действий засчет ролей и даже для сервера не нужно применять динамическую авторизацию.pic-4

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


>> Лучше сделать это на системном языке и чтобы он был кроссплатформенный. Можете что-нибудь посоветовать пожалуйста?
>
>Обычно используют Питон, bash для таких скриптов. Если у тебя код на Питоне, логично в нем сделать CLI скрипт для генерации токенов.
>Если у тебя код на Питоне
Страница выдаётся из Ноды, API я собираюсь переписать на PHP, а код wamp'а на Питоне.


>>1394587
>Такие запросы с джойнами будут плохо работать на больших нагрузках. Они же почти не оптимизируются индексами никак и требуют перебор строк. Возможно, тут придется сделать денормализацию, например, добавить в Dialog либо в Participant дополнительные поля. Чтобы, например, запрос бы имел вид
>
>SELECT FROM Participant WHERE user = ? AND partner = ? AND private =1
>
>Это ложится на индексы. Но, конечно, денормализацию стоит делать во вторую очередь.
Я думаю, что партнер должен добавляться скорее в ссылку на конференцию, потому что получатель это отдельная сущность, которая не отвечает за то с кем она должна вести диалог. А если партнёров будет много (публичная конференция)? Конечно тут можно прийти к созданию отдельной таблицы Partners... не будет ли и здесь плохо работать запрос с джоинами на больших нагрузках?

Далее, даже учитывая что мы создадим поле partner в Conference_Reference, это создаст запрос для получения вида:

// неизвестно какой пользователь сначала "создал" конференцию,
// а какой оказался получателем (партнёром)
SELECT FROM Conference_Reference WHERE (user = sender.id OR user = receiver.id) AND (partner = receiver.id OR partner = sender.id)

Такой запрос может вернуть две записи и сама его форма не элегантна.

Я предлагаю вернуться к идеи выше с массивами >>1387125

SELECT FROM Conference WHERE private = true AND (sender.id = ANY(participants) AND receiver.id = ANY(participants));

Как писалось выше такой запрос использует индексы (https://stackoverflow.com/questions/4058731/can-postgresql-index-array-columns).

У вас есть опыт с работой с индексами. Как вам кажется, индексирование массивов приведёт к желаемому повышению производительности?



>> Можно я оптимизацию БД оставлю на последнее?
>
>Можно. Но я просто хотел подчеркнуть, что мессенджеры это высоконагруженные штуки и там приходится об этом думать. И о шардинге, если ты хочешь, чтобы проект расширялся. Потому мы сначала проектируем нормализованную схему, а потом смотрим на типичные запросы и оптимизируем схему для них.
>
>Более того, часто под мессенджеры даже пишутся специализированные хранилища.
Я это прекрасно понимаю..

>Насчет reply - а недостаточно тут просто сделать поле в сообщении "replyToUuid" со ссылкой на исходное сообщение? Без вложений.
Судя по API телеграма там используется как раз такой подход ( https://core.telegram.org/bots/api#message ), но мне никогда не было понятно почему только можно ответить на одно сообщение, мне иногда хочется ответить на несколько сразу, да и выглядит это как прикрепление к сообщению.

>Так, схема выглядит хорошо. Позже стоит продумать операции, которые будут выполняться (их явно будет больше, например, может понадобиться постраничное получение участников огромного чата). И как их оптимизировать.
Опять же с массивами это тоже делается очень просто

SELECT participants[offset:limit] FROM Conference WHERE id = conference.id;


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


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

https://protonmail.com/support/knowledge-base/how-is-the-private-key-stored/

Я собираюсь повторить эту практику.

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


>> Я думаю, что пока подходы к авторизации методов WAMP не изучены, следует писать сырой код на if'ах, а подход с роутингом взять на заметку и держать в уме. А как думаете вы?
>
>Я, увы, так хорошо авторизацию в WAMP не знаю и сейчас не могу подсказать.
Рано или поздно мы разберёмся какая архитектура будет лучше.

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



Ответы: >>1397655 >>1398326
Аноним 2019/05/11 13:16:23  №1397655 21
>>1397648
Илюша,ты когда трезвый,такой добрый! кек
Аноним 2019/05/12 11:52:50  №1398326 22
>>1397648

> Я теперь понял, что импорты должны выполнятся там где выполняется этот запрос - это работает.

Общий принцип такой, что ты импортируешь только то, что как-то используешь именно в этом файле (вызываешь, создаешь объекты этого класса итд). Если не используешь - то не импортируешь.

> К сожалению, мне не удалось найти какой паттерн наследования использует psql

Эти паттерны наследования реализуются не на уровне Postgres, а на уровне ORM. И там в документации к SQLAlchemy перечислено несколько вариантов на выбор.

> Я косвенно предположил, что это именно он

Это Concrete TI, так как при STI таблица одна, а при Class TI в text_message были бы только те поля, которые отсутствуют в message.

> Использование UNION приводит к нагрузке?

Это надо смотреть EXPLAIN-ом и делать тесты, но скорее всего, да. Представь, что ты хочешь получить последние 10 сообщений: самый быстрый способ сделать это - это пройтись по индексу (где сообщения отсортированы по времени) и взять первые 10 записей из единственной общей таблицы. В случае с UNION нам надо брать по несколько сообщений из каждой таблицы и как-то объединять списки. Это в случае, если СУБД будет работать максимально оптимально, а это не факт.

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

> Отношения между сущностями тяжело совершить с помощью встроенных инструментов и приходится делать метод для совершения запроса в ручную

Вообще, в документации есть похожий пример и там используются дополнительные параметры (foreign_keys): https://docs.sqlalchemy.org/en/13/orm/join_conditions.html#creating-custom-foreign-conditions

Может, в этом проблема?

Ведь ORM надо знать, как связывать объекты между собой. Допустим, у тебя есть объект класса A, и он связан отношением с объектом класса B. Если ты создаешь несколько объектов класса B, добавишь их в класс A, и попробуешь сбросить данные в БД, то ORM должен понять: как сохранить в БД связь между объектами?

Думаю, явное указание foreign_keys и remote_side как раз говорит ORM, что надо взять id из одного поля объекта и прописать его в поле другого объекта.

Но я могу ошибаться, так как лишь бегло пролистал документацию.

> При получении сущностей выдается массив из сначала сущностей родителя, затем сущности ребёнка.

Если ты используешь наследование, то Text_Message - это наследник Message, в соответствие с принципом Лисков наследник можно использовать вместо предка, и когда ты запрашиваешь список Message, то в нем будут и объекты Text_Message. Если ты это не хочешь, то тебе надо указать дополнительное условие (например: искать только Text_Message).

> Сейчас я сделал авторизацию действий засчет ролей и даже для сервера не нужно применять динамическую авторизацию

Только не забудь, что надо использовать принцип "белого списка" и все, что не указано явно - запрещается.

> Лучше сделать это на системном языке и чтобы он был кроссплатформенный

Вообще, "системное программирование" - это написание всяких модулей ядра, драйверов на языках вроде Си. А так, пиши на чем удобнее - на Питоне, JS или bash.

> А если партнёров будет много (публичная конференция)?

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

- получить полный список для маленькой конференции
- получить (по запросу пользователя) полный список для средней конференции
- если у тебя есть большие конференции (> 1000 - 5000 - 10000 польз.), то там может потребоваться получать список постранично

> не будет ли и здесь плохо работать запрос с джоинами на больших нагрузках?

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

> // неизвестно какой пользователь сначала "создал" конференцию,

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

> Как вам кажется, индексирование массивов приведёт к желаемому повышению производительности?

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

SELECT WHERE a = 1 AND b = 2

Такой запрос при использовании UNIQUE INDEX (a, b) будет искать в индексе пару (1; 2) и будет оптимальным. Индекс выглядит так:

(a, b -> id)

Он отсортирован по возрастанию пар значений a; b.

А как выглядит индекс в твоем случае? Скорее всего, как отсортированный список (participant -> id). И как в нем найти соответствующие условию записи? Ищем по participant = sender, получаем большой список id, ищем по participant = receiver, получаем большой список id, ищем в 2 списках одинаковые id.

Твой запрос эквивалентен такому:

SELECT a.id FROM table AS a, table AS b WHERE a.participant = :sender AND b.participant = :receiver AND a.id = b.id

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

Потому я и советовал поступать так:

- сделать нормализованную, не оптимальную схему
- сделать список запросов, которые будут выполняться
- оптимизировать схему под них

> но мне никогда не было понятно почему только можно ответить на одно сообщение,

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

> Опять же с массивами это тоже делается очень просто

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

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

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

Шифрование ключа паролем - это симметричное шифрование. Пароль относительно короткий и владелец сервера может попытаться его сбрутфорсить. Особенно, если у него есть "друзья" из NSA с дата-центрами, построенными специально для брутфорса паролей. Но можно придумать более интересные схемы синхронизации: 2 устройства одного пользователя одновременно выходят в сеть и передают ключ друг другу с использованием асимметричного шифрования, так, что сервер не может увидеть передаваемый ключ (т.к. при асиметричном шифровании используется случайный очень длинный ключ, как в HTTPS).

Это просто рассуждения на тему того, что тут можно сделать в теории.