CodeWay — Путь кода
17 subscribers
30 photos
22 videos
8 files
16 links
Здесь пишут о разработке на PHP, Laravel и всём, что с этим связано.
Автор канала — веб-разработчик Алексей Васильев.
Download Telegram
Channel photo updated
Глупые ошибки бывают и в популярных фреймворках

На днях был случай — упала ошибка на одном из проектов с Laravel 6. По стектрейсу понятно, что проблема находится в вендорском коде, а именно в /vendor/Illuminate/Cookie/Middleware/EncryptCookies.php. Полез туда, в метод decrypt(), который выглядит следующим образом.

/**
* Decrypt the cookies on the request.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* @return \Symfony\Component\HttpFoundation\Request
*/
protected function decrypt(Request $request)
{
foreach ($request->cookies as $key => $cookie) {
if ($this->isDisabled($key)) {
continue;
}

try {
$value = $this->decryptCookie($key, $cookie);

$hasValidPrefix = strpos($value, CookieValuePrefix::create($key, $this->encrypter->getKey())) === 0;

$request->cookies->set(
$key, $hasValidPrefix ? CookieValuePrefix::remove($value) : null
);
} catch (DecryptException $e) {
$request->cookies->set($key, null);
}
}

return $request;
}

Ошибка в той строчке, которая начинается с $hasValidPrefix =..., первый аргумент strpos() неправильного типа. Первый аргумент — $value. Сигнатура strpos совершенно недвусмысленна: strpos(string $haystack, string $needle, int $offset = 0): int|false, $value должно быть строкой, но ей не является. Поскольку $value получается из метода decryptCookie(), стоит взглянуть туда. И действительно, в аннотации указано:

/**
* Decrypt the given cookie and return the value.
*
* @param string $name
* @param string|array $cookie
* @return string|array
*/

т.е. он может возвращать и массив. На месте же возвращаемое значение на массив нигде не проверяется, потому-то и возникает ошибка типа.
Справедливости ради отмечу, что в восьмой версии фреймворка этот метод починили, добавив в первый if() проверку на массив, так что если кука — массив, до проблемного места выполнение в принципе не доходит. Но мне надо было исправить в шестой версии.
Я воспользовался тем, что это middleware наследуется в пользовательском коде (App\Http\Middleware\EncryptCookies), скопировал туда родительский метод и перенёс исправление из 8-й версии. Проблема решена.
Как работать с деревом элементов, хранимых в плоском виде в базе данных?

Такая задача часто встаёт перед разработчиками. Представим себе примерную структуру таблицы items, в которой хранятся элементы дерева.
INTEGER id
VARCHAR name
INTEGER parent
Дополнительные поля могут присутствовать, но сейчас они нам не так интересны.

Какие операции могут требоваться от такого дерева?
1) Получение родителя для данного элемента
2) Получение потомков для данного элемента
3) Получение всех нижележащих элементов, включая вложенные
4) Получение всех предков данного элемента (например, для хлебных крошек)
5) Получение элементов-братьев данного элемента
6) Работа с уровнем вложенности
и так далее. При прямой работе с БД (или даже через ORM) каждая из этих задач, не считая первых двух, требует минимум нескольких дополнительных запросов. Например, братья получаются так ($target — целевой элемент, $parent — получаемый первым запросом родительский):
SELECT id, name, parent FROM items WHERE id = {$target->parent}
SELECT id, name, parent FROM items WHERE parent = {$parent->id} AND id != {$target->id}
Вместо двух запросов можно сделать один со вложенным внутри, но суть от этого не меняется. В остальных случаях ситуация хуже (особенно по пункту 3).

Возникает вопрос: можно ли как-то не делать все эти дополнительные запросы? Как оказывается, можно. При создании класса я исходил из следующих предпосылок:
1) элементы должны получаться однажды;
2) перебор всех элементов тоже должен происходить однажды, и тогда же выстраиваться внутренняя структура дерева.
Для каждого элемента выстраивается полный путь в виде строки из серии “2.5.7”, то есть предками элемента с id = 7 являются элементы с id = 2 и 5. Вот, как это происходит (Tree.php, строка 79).
$this->tree[$child->id] = $this->tree[$item->id].'.'.$child->id;
$this->tree — массив путей, $item — очередной родительский элемент, $child — очередной дочерний элемент. Если путь элемента 8 был «3.8», то дочерний 11-й присоединяет к родительскому пути «.11».

Все операции над деревом выполняются путём оперирования с массивом путей $this->tree и элементов $this->items. Никаких дополнительных запросов к БД не требуется. Пример с поиском всех нижележащих элементов:
/**
* Возвращает нижележащие элементы для данного (дочерние и ниже)
*
* @param Item $item Целевой элемент
* @return array<Item>
*/
public function getDescendants(Item $item): array
{
$search = $this->getDescendantHashes($item);
$keys = array_keys($search);
$selectedItems = $this->getItemsByIds($keys);
return $selectedItems;
}

/**
* Ищет хэши нижележащих элементов
*
* @param Item $item
* @return array
*/
protected function getDescendantHashes(Item $item): array
{
$pattern = '/^'.$this->tree[$item->id].'\..+/s';
$search = preg_grep($pattern, $this->tree);
return $search;
}

Демонстрационный код я выложил на GitHub. Класс Item подставлен исключительно для примера, класс Tree легко адаптируется для всех случаев, когда элемент может предоставить атрибуты id и parent.
👍2
Где и как читаются файлы конфигурации

Внутренние процессы фреймворка, как правило, скрыты от глаз — и это правильно, потому что задача фреймворка в том и состоит, чтобы избавить разработчика от возни с низкоуровневым кодом. Но иногда бывает полезно знать, где и как именно происходит тот или иной процесс. Сегодня разберёмся с чтением конфигурации в Laravel.

Получилось длинно, потому читайте здесь: https://telegra.ph/Gde-i-kak-chitayutsya-fajly-konfiguracii-04-10
👍1
Отвязаться от конкретной инфраструктуры

Сложно себе представить веб-проект, который обходился бы только теми ресурсами, которые заложены в нём самом. Даже если вы не задействуете никаких внешних API, то хотя бы одна база данных у вас точно есть. И да, это тоже внешняя инфраструктура, потому что происходящее внутри БД вы не контролируете.

«Ну есть инфраструктура, и есть, в чём проблема?» — спросите вы и будете правы. Тут весь вопрос в том, как именно организовано обращение к ней. На начальном этапе разработки проекта часто в коде фигурирует обращение напрямую, используя классы и методы, специфичные для текущей версии инфраструктуры. Давайте возьмём ту же БД — пусть у вас MariaDB и она заведена как дефолтное соединение mysql (напоминаю, в Laravel соединение с БД описывается в config/database.php). Для получения, допустим, ведущихся школьных предметов вы пишете в нужных местах примерно такой запрос.
$subjects = DB::table('subjects')->where('active', 1)->get();

Возможные проблемы с этим:
1) Копипаста. Везде, где вам нужно получить эти данные, приведённую строчку придётся дублировать.
2) Если вы захотите получать предметы из другой БД, вам надо указать или поменять соединение (->connection()) во всех местах, где подобные запросы используются.
3) Если же потребуется получать их совсем из другого источника (API, микросервис), то все такие места придётся полностью переписать.
4) Код, зависящий от инфраструктуры, труднее тестировать, потому что он не вполне предсказуем. Например, тесты могут поломаться из-за неполадок в конкретной физической БД.

Что можно с этим сделать?
Часть проблем в случае с БД решает ORM, абстрагирующая некоторые общие моменты — будь то соединения или типовые фильтры. Но это частичное и временное решение, для другой инфраструктуры оно и вовсе не подходит.
Для начала надо сформулировать, что именно для нас должен делать элемент инфраструктуры (назовём его сервисом). В нашем случае речь идёт об одном действии — «получить активные предметы», параметров нет, а возвращается коллекция. Каждое из действий станет методом будущего интерфейса. Интерфейс будет выглядеть примерно так.

<?php

namespace App\Contracts;

use Illuminate\Support\Collection;

interface SubjectsRepositoryContract
{
public function getActiveSubjects(): Collection
}

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

<?php

namespace App\Repositories;

use App\Contracts\SubjectsRepositoryContract;

class DatabaseSubjectsRepository implements SubjectsRepositoryContract
{
public function getActiveSubjects(): Collection
{
return DB::table('subjects')→where('active', 1)→get();
}
}

Как же нам теперь использовать новые классы в коде? Обращаться будем к интерфейсу.

<?php

//controller

use App\Contracts\SubjectsRepositoryContract;
use Illuminate\View\View;

public function showSubjects(SubjectsRepositoryContract $subjectsRepository): View
{
$subjects = $subjectsRepository->getActiveSubjects();
return view('subjects.list', compact('subjects'));
}

Осталось настроить привязку в контейнере.

<?php

//AppServiceProvider.php

use App\Contracts\SubjectsRepositoryContract;
use App\Repositories\DatabaseSubjectsRepository;
...
App::bind(SubjectsRepositoryContract::class, DatabaseSubjectsRepository::class);

В контроллер будет подставляться нужная реализация за счёт внедрения зависимостей, и в случае необходимости мы можем её заменить — временно или постоянно. Во время тестирования можем подставить заглушку с фиксированными данными, например.
Если же в вашем фреймворке/движке нет контейнера, то вся разница будет в том, что реализацию придётся создавать и передавать вручную, остальное работать будет так же.
Итоги
Чтобы отвязаться от конкретного варианта сервиса, нужно:
1) формализовать выполняемые над ним операции;
2) создать на основе этого списка интерфейс;
3) переделать все обращения к сервису на обращения к соответствующим методам интерфейса;
4) детали работы с сервисом перенести в класс-реализацию интерфейса;
5) при наличии контейнера настроить в контейнере привязку реализации к интерфейсу, иначе создавать соответствующий класс и передавать в методы вручную.
Eloquent: за и против

Как и в любом серьёзном фреймворке, в Laravel есть встроенная ORM — она называется Eloquent. Предполагается, что разработчик будет использовать именно её. Но хороший специалист всегда знает границы применимости своих инструментов. Потому давайте разберёмся, какие за и против есть у этой ORM.

https://telegra.ph/Eloquent-za-i-protiv-04-23
Трудности при использовании массивов

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

Если говорить в целом, то массив, который собран не сейчас и/или не вами, представляет собой настоящий чёрный ящик, о котором толком ничего неизвестно, кроме того, что содержащиеся в нём элементы в принципе можно перебрать.

1. Неизвестны тип и последовательность ключей.
PHP позволяет делать ключи массива числовыми или строковыми (и смешивать эти типы), и расположение их может быть любым. Нет никакой гарантии, что в массиве существует тот или иной ключ, вам придётся каждый раз проверять это вручную.

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

3. Неизвестны типы элементов (не говоря уже о значениях).
Каждый элемент нужно проверять, насколько он соответствует нужным нам критериям, и затем решать, что с ним делать, если он не подходит. Даже сравнительно дёшево узнать полный набор типов элементов без полного их перебора мы и то не можем.

4. Как следствие предыдущего, неизвестна вложенность.
Любой элемент массива может быть ещё одним массивом, и так далее до бесконечности. Обойти каждый элемент этого "дерева" — задача нетривиальная.

Что в итоге?
Я призываю вас подходить к использованию массивов с должной осторожностью и, где возможно, не применять их в чистом виде — например, применять классы-обёртки, которые обеспечивают большую предсказуемость организации данных. Как вариант — типизированные коллекции, в которые можно добавить только элементы одного типа. Или ValueObject-ы с жёстко заданными свойствами и их типами (вместо массива с несколькими именованными значениями для передачи в другой модуль проекта).
👍1
Создать свой composer-пакет: общий план

На фига козе баян Зачем вам ввязываться в это мероприятие, вы должны решить для себя сами. Я буду считать, что по какой-то причине вы уже решились взяться за пакет, но не знаете, что делать дальше. Что ж, в добрый путь. Ну и да, у меня есть уже свой пакет (https://packagist.org/packages/garmonic/laravel-features-arch), так что кое-что об этом процессе мне известно, поверьте.

https://telegra.ph/Sozdat-svoj-composer-paket-obshchij-plan-05-14

#пакеты #composer #php #laravel
👍1
Объединить нельзя дублировать

Итак, ситуация. У вас есть два проекта, на каждом из которых есть схожий функционал. Функционал этот задействует одно и то же хранилище данных, но всё остальное на проектах своё. Поступила задача, чтобы внутренняя часть этого функционала (бэкэнд) работала строго единым образом. Какие есть варианты архитектуры?

1. Привести код на одном из проектов к актуальным требованиям, на второй скопировать.

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

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

2. Оставить актуальный код на одном проекте, со второго вызывать.

Плюсы:
- никакого дублирования.

Минусы:
- появляется зависимость вызывающего проекта от вызываемого. При проблемах со связью функционал перестанет работать.

3. Сохранить непосредственную отработку запроса на обоих проектах, а побочную (например, обработку события) на одном, со второго вызывать.

Плюсы:
- сохраняется известная независимость проектов.

Минусы:
- если побочная часть функционала так же важна для бизнес-логики, как и основная, то вариант аналогичен 2-му: частичная обработка запроса = отсутствие его обработки.

4. Вынести весь функционал на отдельный внешний сервис (API), с обоих проектов вызывать его.

Плюсы:
- отсутствие дублирования, подлинная единая ответственность;
- проекты по-прежнему независимы.

Минусы:
- логику кода придётся прилично переделать в обоих местах;
- даже если подходящий сервис уже есть, функционал нельзя будет перенести «как есть», адаптация понадобится. Если же API надо создавать с нуля, то это дополнительный геморрой свои дополнительные затраты;
- проблемы со связью никто не отменял — API точно так же может отвалиться, и тогда функционал не будет работать ни на одном из проектов.

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

Чтобы не было недопонимания: я имею в виду экземпляры или наследников стандартного PHP-класса Exception, либо Throwable. Так что же с такими объектами делать?

Ответ зависит от того, в каком вы контексте и откуда объект исключения берётся.

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

    throw new Exception('Что-то пошло не так...')

... но именно, что вроде. Лучше писать подробно, что случилось, да и класс исключения описать свой собственный.
Перейдём к вариантам, когда исключение генерируется не вами.

2. Обработать.
Вы ожидаете, что в вызываемом вами коде что-то может поломаться, и пишете, например,

    try {
// основная логика
} catch (LogicException $exception) {
// обработка исключения
}

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

try {
// основная логика
} catch (LogicException $exception) {
return response()->json(['success'=>false, 'message'=>'Переданные данные ошибочны'], 400);
}

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

try {
// основная логика
} catch (LogicException $exception) {
throw new CustomException($exception->getMessage()); // вариант 1
//или
throw new CustomException('Текст ошибки', 0, $exception); // вариант 2
}

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

    // Может быть выброшено исключение, не нужно обрабатывать, пусть валится в 500-ю
$result = someAction();

Так что смотрите по ситуации и выбирайте тот вариант, который для неё наиболее уместен.
👍2
Когда и как делить источник данных на два

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

Как возникает проблема
На этапе проектирования и начальной стадии существования проекта одного хранилища данных каждого типа вполне достаточно: скажем одна MariaDB-база, инстанс Redis для кэша и файловое хранилище типа S3. Ничего лишнего, удобно и понятно, как с этим всем работать. Но, если вы всё правильно делаете и пользователи довольны вашим сервисом, их число постепенно растёт. Запросов к вашим ресурсам в секунду становится всё больше — например, начинали с 5, потом стало 10, 15, 20, 30, наконец, 50. Через день заглядывает хмурый сисадмин. Он бурчит, что сервак не справляется, и надо с этим что-то делать. И с каждым разом его голос звучит всё более мрачно.

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

Адаптация кода
По поводу инфраструктуры разобрались, а как же код? Ведь он был написан с расчётом на один источник данных. Его придётся адаптировать под новые реалии. Если обращения к источнику данных собраны в ряде компактных классов (типа *Repository), то доделать их будет легко — указать в них другое соединение или файловую систему. Возможно, просто поменять где-то привязки в контейнере. Иначе придётся перерывать весь проект, чтобы провести такие изменения. Но так или иначе должно быть введено правило: читать из источника "для чтения", писать в источник "для записи".

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

Итоги
1. Делить хранилище данных надо тогда, когда нагрузка на него потенциально приближается к критической (ну или несколько раньше).
2. В первую очередь стоит разграничить запись и чтение, и распределить ресурсы соответствующих источников в зависимости от нагрузки на соответствующий поток.
3. После изменения инфраструктуры потребуется адаптация кода по правилу "читать из источника на чтение, писать в источник на запись".
4. При решении архитектурного вопроса в связи с адаптацией стоит рассмотреть вариант с CQRS — насколько он применим и реализуем в вашем случае.
👍1
Когда пригодятся типы-перечисления

Вопрос стал особенно актуальным в связи с появлением таких типов в PHP 8.1. Теперь они нативно поддерживаются языком.
Короткий ответ: перечисления применимы везде, где значение переменной должно быть выбрано из закрытого (и достаточно ограниченного) списка.

Вот пример перечисления для цветов светофора. Кроме как "красный" (TrafficLightColor::Red), "жёлтый" (TrafficLightColor::Yellow), "зелёный" (TrafficLightColor::Green), ничего выбрать невозможно. Это может быть очень удобно.

<?php
enum TrafficLightColor
{
case Red;
case Yellow;
case Green;
}
?>

Теперь поясню подробнее.
В разработке то и дело приходится иметь дело с такими специфическими данными, типа статусов. Каково может быть состояние письма, например? "Подготовлено", "Отправлено", "Не удалось отправить". Четвёртого не дано. Или, как вариант, источники данных для приходящих картинок. Вряд ли их будет даже десять. И вот что со всем этим делать?

Глобальных проблем тут две: валидация и понимание происходящего.

По поводу валидации. Если мы храним значения перечислимого типа как строки или числа, мы вынуждены каждое приходящее в соответствующую переменную значение проверять — укладывается ли оно в наше перечисление? А что, если просто случайно совпало, а строка/число означает совсем другое? Обычная проблема использования скалярных типов не по назначению. В таких случаях переходят к ValueObject-ам (в которых и пишется валидация), но Enum-ы куда удобнее для работы с перечислениями.

Что же касается понимания, то трудности здесь схожие. Мы не можем круглосуточно помнить, что эта строка (или число) соответствуют элементам ограниченного множества, и в один прекрасный момент туда обязательно попадёт что-то не то — хотя бы из-за опечатки.

<?php
const MAIL_STATUSES = ['prepared', 'sent', 'sending_failed'];
//...
$result = Mail::send($letter);
if($result->status == 'send') { // Всего одна буква разницы, а условие уже работает неправильно
return response()->json(['message'=>'Письмо отправлено']);
} else {
return response()->json(['message'=>'Ошибки при отправке']);
}

И вот, почта уходит, а сообщение говорит об обратном. Были бы эти статусы вариантами Enum, то даже редактор уже ткнул бы разработчика в ошибку, указав, что такого варианта нет.

Вместо вывода: если вы уже перешли на PHP 8.1 и работаете с подобными значениями из ограниченного списка, присмотритесь к Enum.
К вопросу о доступе

Практически для каждой системы актуален вопрос контроля доступа к информации и к манипуляциям с ней. Предположим для простоты, что речь идёт о полноценном сайте — с API немного другая история, но глобальных отличий мы не найдём.

В конечном счёте, мы должны уметь отвечать на такой вопрос: может ли пользователь с такими-то данными сеанса выполнить такое-то действие? Что делать в случае отрицательного ответа — вопрос вторичный и зависит от требований заказчика. Чаще всего либо ставится редирект на другую страницу (главную, например), либо отдаётся 403 статус/страница ошибки с указанием, что не так.

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

1. Авторизован ли пользователь.
Самая простая и понятная тема. Такая проверка присутствует если не на всех сайтах, то на 99% точно. Например, может быть закрыта страница профиля или возможность комментировать без авторизации.

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

3. Относится ли пользователь к определённой группе (имеет определённую роль).
Такое применяется на более сложных сайтах, где много пользователей с разными правами (user, manager, admin). Подобная система требует заведения дополнительных таблиц в БД и модуля контроля/изменения принадлежности пользователя к группе. Возможно, его придётся написать. Даже если этот модуль берётся готовым, он требует настройки путём указания ответов на такие вопросы:
- Может ли быть у пользователя больше одной роли?
- Как именно назначаются первые роли?
- Кто вправе менять/добавлять роль пользователя и по каким правилам?
- Какая логика должна выполняться при изменении/добавлении роли?

4. Имеет ли пользователь определённое разрешение?
Это ещё более сложная история. Разрешения делаются максимально дробными (буквально, отдельное на каждое возможное действие) и заводятся в виде отдельных сущностей. Чаще всего система разрешений соседствует с системой групп. Например, разрешения привязываются к группам, и список тех разрешений, которые есть у пользователя, получаются на основе списка групп, в которых он состоит. Но встречаются такие случаи, когда сверх разрешений по группам пользователю могут выдаваться ещё какие-то персональные, и их тоже надо учитывать при контроле доступа.

5. Соответствуют ли данные запроса некоторым требованиям?
Я здесь имею в виду метаданные — IP-адрес, заголовки, куки и тому подобное. Например, определять страну или регион по IP-адресу и редиректить на соответствующий раздел (ну или показывать, что в тех местах мы пока не работаем).

Если вы знаете ещё о каких-то проверяемых при контроле доступа условиях, напишите об этом в комментариях.
👍1
ArchDays Recap: впечатления от митапа

Митап прошёл в очном формате 27 июля в БЦ "Академик" на проспекте Вернадского. Я присутствовал там лично и хочу поделиться впечатлениями.

Финальная программа митапа выглядела примерно так:

1. Алексей Маликов. О чём я не думал три года назад. (в докладе о распиле монолита)
2. Сергей Баранов. Многоликий DDD.
3. Евгений Пешков. Про агрегаты.

Митап был задуман как нечто, идущее "по следам" прошедших конференций по архитектуре ArchDays. Собственно, это было заложено в самом названии: Recap означает "резюме, краткое повторение". И замысел этот очень сказался на двух докладах из трёх (1-м и 3-м), что в моём случае было скорее к худшему. Ведь конференций я в глаза не видел, а просто заинтересовался мероприятием самим по себе. Потому ценность третьего выступления, на мой взгляд, была сильно ниже — Евгений только ответил на частый вопрос из поступавших ему после прошлого выступления (про границы агрегатов — я бы назвал их классами сущностей), после чего предложил задавать новые. Хотя мысль о том, что в классе-агрегате в первую очередь надо обращать внимание на консистентность данных, потом на читаемость кода, и лишь потом на производительность, прозвучала очень ясно и я отметил её для себя.

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

Доклад Сергея Баранова на фоне других выглядел подлинным "основным блюдом" и мог бы украсить любую подходящую конференцию. Докладчику удалось полноценно погрузить аудиторию в одну из практик DDD (Domain Driven Design) — ивент-шторминг. Про DDD мои представления пока довольно поверхностны и ограничиваются минимальной теорией, так что мне было интересно лучше узнать детали этой концепции. Если вкратце, то ивент-шторминг — это некое собрание бизнес-экспертов с техническими с целью выработки общего понимания бизнес-процессов компании. Максимально простым и понятным способом, буквально на стикерах. События, команды и правила пишутся на стикерах разных цветов, образовывается большая карта деятельности бизнеса, которая затем делится на ряд ограниченных контекстов (термин DDD). Хорошее понимание системы приносит пользу буквально каждому участнику IT-процессов, от тестировщика до CIO (IT-директора). Сергей отдельно остановился на том, в чём именно эта польза заключается для того или иного специалиста.

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

#code_way #archdays_recap #ddd
Ряд фотографий с ArchDays Recap