#ревью
Перед отправкой кода на проверку, проверь свой код как-будто это чужой код. Проверяй не сразу, а на следующий день или после решения другой задачи
Перед отправкой кода на проверку, проверь свой код как-будто это чужой код. Проверяй не сразу, а на следующий день или после решения другой задачи
#Тестирование
Не забывай делать тесты максимально приближенными к боевым условиям.
С начало может показаться, что это ерунда и можно где-то просто вбить поле от балды или скопировать данные откуда-то переделав одно поле или текущий тест чуть исправить.
Но в дальнейшем это приводит как правило к большой путаницы, и лучшая стратегия в написание тестов:
1 случай = 1 тест
Не забывай делать тесты максимально приближенными к боевым условиям.
С начало может показаться, что это ерунда и можно где-то просто вбить поле от балды или скопировать данные откуда-то переделав одно поле или текущий тест чуть исправить.
Но в дальнейшем это приводит как правило к большой путаницы, и лучшая стратегия в написание тестов:
1 случай = 1 тест
Экшен – это слой, который находится перед сервисом и совершает какое-то полезное действие, например сохранить или обновить данные.
Сервисы на проекте дробятся на экшены, в экшен попадают методы, которые совершают, какие-то действия, и методы, которые помогают сделать нам эти действия, например подготовить данные. Общие методы для экшенов остаются в сервисе, например перобразованиt доларовjq единицы в центы. Экшен обычно наследуется от сервиса, но может и не наследоваться, если экшен уникальный и сервиса не существует.
#ООп #слоистая_архитекрутра #laravel
Сервисы на проекте дробятся на экшены, в экшен попадают методы, которые совершают, какие-то действия, и методы, которые помогают сделать нам эти действия, например подготовить данные. Общие методы для экшенов остаются в сервисе, например перобразованиt доларовjq единицы в центы. Экшен обычно наследуется от сервиса, но может и не наследоваться, если экшен уникальный и сервиса не существует.
#ООп #слоистая_архитекрутра #laravel
Forwarded from Denis Pankratov
TheTenn_опыт_микросервисной_архитектуры.pdf
5.6 MB
Презентация с митапа
Forwarded from Anton Zabolotnikov
С самого начала проекта у нас было несколько микросервисов и так как у нас было только мобильное приложение - это затрудняло отслеживание запросов, сделанных к нашему серверу.
Для решения этих проблем мы стали использоват Телескоп
В телескопе очень удобно отслеживать что происходит с запросом.
- можно отследить вызванные эндпоинты со всеми входными и выходными параметрами
- увидеть какие джобы тригерятся в процессе выполнения запроса,
- Посмотреть HTTP запросы отправленные в сторонние сервисы, а так же ответы от сторонних API
- Так же можно увидеть какие запросы отправленны в базу данных,
- Телескоп запишит все иключения которые мы получили
- и в итоге увидим что получить фронт после исполнения запроса.
в development и production окружениях мы используем разные конфигурации телескопа. В production окружении нам не было смыла отслеживать абсолютно все события, происходящие в системе и мы решили отслеживать только возникающие проблемы в ходе эксплуатации приложения.
На деве мы пишем все запросы которые приходят. что помогает нам легче и быстрее тестировать релазиованный функционал.
Так как у нас мироксервисы, а телескоп должен быть настроен на всех сервисах одинакого, конфигурацию мы перенесли в пакет, что нам так же помогло быстрее обновлять фильтры, исключать не нужные запросы на проде и на дев окрпужение, быстрее обновлять конфигурации на всех сервисах
При конфигурации мы столкнулись с интересной проблемой мониторинга Job. Так как фильтр работает таким образом, что в него попадет сущность при создании - то все джобы попадающие в фильтр всегда попадали в него только в pending статусе, что не позволяло нам оставлять в телескопе только зафейленые джобы. поэтому мы решили реализовать логику очистки completed джоб отдельно, что позволило нам видеть в проде только упавшие или pending джобы. .
Так же сделали очень удобное изменения БД телескопа, поле content перевели из текстового в JSON, что помогло нам быстрее искать нужную инофрмацию о джобах с помощью SLQ Запросов.
собственно это все наши приключения с телескопом.
Что можно сказать об опыте использования? Телескоп помог нам быстро находить и устранять проблемы с багами. Всё видно, всё удобно.
Для решения этих проблем мы стали использоват Телескоп
В телескопе очень удобно отслеживать что происходит с запросом.
- можно отследить вызванные эндпоинты со всеми входными и выходными параметрами
- увидеть какие джобы тригерятся в процессе выполнения запроса,
- Посмотреть HTTP запросы отправленные в сторонние сервисы, а так же ответы от сторонних API
- Так же можно увидеть какие запросы отправленны в базу данных,
- Телескоп запишит все иключения которые мы получили
- и в итоге увидим что получить фронт после исполнения запроса.
в development и production окружениях мы используем разные конфигурации телескопа. В production окружении нам не было смыла отслеживать абсолютно все события, происходящие в системе и мы решили отслеживать только возникающие проблемы в ходе эксплуатации приложения.
На деве мы пишем все запросы которые приходят. что помогает нам легче и быстрее тестировать релазиованный функционал.
Так как у нас мироксервисы, а телескоп должен быть настроен на всех сервисах одинакого, конфигурацию мы перенесли в пакет, что нам так же помогло быстрее обновлять фильтры, исключать не нужные запросы на проде и на дев окрпужение, быстрее обновлять конфигурации на всех сервисах
При конфигурации мы столкнулись с интересной проблемой мониторинга Job. Так как фильтр работает таким образом, что в него попадет сущность при создании - то все джобы попадающие в фильтр всегда попадали в него только в pending статусе, что не позволяло нам оставлять в телескопе только зафейленые джобы. поэтому мы решили реализовать логику очистки completed джоб отдельно, что позволило нам видеть в проде только упавшие или pending джобы. .
Так же сделали очень удобное изменения БД телескопа, поле content перевели из текстового в JSON, что помогло нам быстрее искать нужную инофрмацию о джобах с помощью SLQ Запросов.
собственно это все наши приключения с телескопом.
Что можно сказать об опыте использования? Телескоп помог нам быстро находить и устранять проблемы с багами. Всё видно, всё удобно.
Forwarded from Anton Zabolotnikov
Обычно на проектах очередь тестируется прямо в одном тесте с запросом в синхронном режиме. или же мы фейкаем джобы, и проверяем была ли вызвана джоба.
На проекте theTenn-а мы работаем с большим количеством сторонних API сервисов, у нас встречается много уведомлений: такие как: сообщения в слак, пуши, отправка емайлов.
Обычно все такие операции обернуты в джобы,
И при тестирование запроса у нас получался достаточно грамоздкий тест ,
А если проверять всю логику эндпоинта с разными выходными данными, то обычно создаетс несколько объемных тестов.
Что бы разгрузить тесты и снизить объем проверок в одном тесте кейсы мы решили выносить тестирование джоб в отдельные тест кейсы.
Как это происходит?
Мы фейкаем джобы, которые нужно проверить в отдельном тесте и проверяем только состояние задиспатченых джоб, состояние джобы мы сверяем с фикстурой.
См слайд
А потом уже в отдельных нескольких компактных тестах мы проверяем логику обернутую в Job.
В этих тестах мы вызываем Job в синк режиме, проверяем HTTP вызовы, что записалось в БД и тп, как и в тестах эндпоинта .
Теперь у нас тесты получаются достаточно компактными,
что помогает быстрее разобраться для чего написан тест и такой подход помогает избегать неожиданных сотояний Job.
На проекте theTenn-а мы работаем с большим количеством сторонних API сервисов, у нас встречается много уведомлений: такие как: сообщения в слак, пуши, отправка емайлов.
Обычно все такие операции обернуты в джобы,
И при тестирование запроса у нас получался достаточно грамоздкий тест ,
А если проверять всю логику эндпоинта с разными выходными данными, то обычно создаетс несколько объемных тестов.
Что бы разгрузить тесты и снизить объем проверок в одном тесте кейсы мы решили выносить тестирование джоб в отдельные тест кейсы.
Как это происходит?
Мы фейкаем джобы, которые нужно проверить в отдельном тесте и проверяем только состояние задиспатченых джоб, состояние джобы мы сверяем с фикстурой.
См слайд
А потом уже в отдельных нескольких компактных тестах мы проверяем логику обернутую в Job.
В этих тестах мы вызываем Job в синк режиме, проверяем HTTP вызовы, что записалось в БД и тп, как и в тестах эндпоинта .
Теперь у нас тесты получаются достаточно компактными,
что помогает быстрее разобраться для чего написан тест и такой подход помогает избегать неожиданных сотояний Job.
Заметка будущим инжинерам.
никогда не воспринимайте текст задачи как что-то отдельное от проекта, задача на проекте ставится для решения проблема заказчика, бизнеса. Нужно подходить к поставленной задачи с точки зрения бизнеса, а никак не основываясь на том что написано в программе.
Для меня было открытием, хотя я наверное и сам такой был, когда начинал свой путь, что ребята решают проблемы не операясь на контекст задач.
никогда не воспринимайте текст задачи как что-то отдельное от проекта, задача на проекте ставится для решения проблема заказчика, бизнеса. Нужно подходить к поставленной задачи с точки зрения бизнеса, а никак не основываясь на том что написано в программе.
Для меня было открытием, хотя я наверное и сам такой был, когда начинал свой путь, что ребята решают проблемы не операясь на контекст задач.
В PHP операторы сравнения
Например:
==
и ===
используются для различных целей:==
(равно): Проверяет равенство значений без учета типа данных. Например:
$a = 5;
$b = '5';
if ($a == $b) {
echo "Значения равны";
}
Этот код выведет Значения равны, потому что PHP приведет строку '5' к числу 5 для сравнения.
=== (строгое равно): Проверяет равенство значений с учетом типа данных.
Например:
$a = 5;
$b = '5';
if ($a === $b) {
echo "Значения и типы данных равны";
} else {
echo "Значение или тип данных не равны";
}
Этот код выведет Значение или тип данных не равны, потому что $a является числом, а $b является строкой.
Таким образом, == используется для нестрогого сравнения, а === — для строгого сравнения с учетом типа данных.
немного о рефакторинге и нейросетях
Даже если вы загоняете задачу в нейросеть и просите её сгененировать код, то проверяйте этот код,
на проекте у нас сотрудник пологаю именно так сделал.
Давайте приведем код в понятный вид и избавимся от кучи ненужных действий, нейросеть это модель мозга человека, мозг человека не совершенен, а нейросети это в полне себе еще очень мальенкий и юный человек, который допускает ПОСТОЯННО ошибки.
Не доверяю нейросетям, использую их как поиск
Было:
Стало
В итоге урпростилась сложность с квадратичной до линейной
Даже если вы загоняете задачу в нейросеть и просите её сгененировать код, то проверяйте этот код,
на проекте у нас сотрудник пологаю именно так сделал.
Давайте приведем код в понятный вид и избавимся от кучи ненужных действий, нейросеть это модель мозга человека, мозг человека не совершенен, а нейросети это в полне себе еще очень мальенкий и юный человек, который допускает ПОСТОЯННО ошибки.
Не доверяю нейросетям, использую их как поиск
Было:
public function uploadOnePhoto(array $data): Model
{
return $this->repository->create($data);
}
public function uploadPhotos(int $eventId, array $data): array
{
foreach ($data as $key => $value) {
$data[$key]['event_id'] = $eventId;
}
return array_map([$this, 'uploadOnePhoto'], $data);
}
Стало
public function uploadPhotos(int $eventId, array $data): array
{
return Arr::map($data, fn ($media) => $this->repository->create([
'event_id' => $eventId,
'media_id' => $media['media_id'],
]));
}
В итоге урпростилась сложность с квадратичной до линейной
Этот код я должен добавить в подборку шедевров
лайкни если понял в чем шедевральность
private function needToRestrictSearchOutput(string $userIp): bool
{
return $userIp && (!$this->user || $this->isAdmin());
}
лайкни если понял в чем шедевральность
Написа как-то подавану. сохраню себе
Привет
Смотри:
ты говоришь методом получить ID девайса, значит метод должен вернуть ID девайса. а не сам девайс, если бы было getDevice, то объект девайса уместен.
а этот метод у тебя возвращает объект Модели. какой модели сразу хочется спросить?
Он у тебя должен возвращать INT.
Дальше.
A если возвращаешь объект, то надо бы уточнять какой объект. а то везде можно написать
Чем конкретнее будут типы, тем легче будет не запутаться. Плюс четко определенный тип спасает от невиданных багов, и потом если баг возник то его отловить будет легче
Привет
Смотри:
$this->getDeviceId(Auth::id(), $advertisingId)
protected function getDeviceId(int $userId, ?string $advertisingId): Model
ты говоришь методом получить ID девайса, значит метод должен вернуть ID девайса. а не сам девайс, если бы было getDevice, то объект девайса уместен.
а этот метод у тебя возвращает объект Модели. какой модели сразу хочется спросить?
Он у тебя должен возвращать INT.
Дальше.
A если возвращаешь объект, то надо бы уточнять какой объект. а то везде можно написать
object
и потом вообще не разберемся что откуда и куда. Чем конкретнее будут типы, тем легче будет не запутаться. Плюс четко определенный тип спасает от невиданных багов, и потом если баг возник то его отловить будет легче
Не знаю, публиковал или нет, но вот это Мидлвара для идомпотентных запросов. Пусть полежит тут, может достану когда-нибудь из пыльного чулана
<?php
namespace App\Http\Middleware;
use Auth;
use Cache;
use Carbon\Carbon;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\HttpException;
class IdempotenceRequest
{
public function handle(Request $request, Closure $next)
{
$lock = Cache::lock($this->makeName($request), Carbon::SECONDS_PER_MINUTE);
if (!$lock->get()) {
throw new HttpException(Response::HTTP_CONFLICT, __('validation.exceptions.already_in_progress'));
}
try {
return $next($request);
} finally {
$lock->release();
}
}
protected function makeName(Request $request): string
{
return hash('sha512', implode('', [
$request->getPathInfo(),
$request->getContent(),
Auth::id(),
]));
}
}
## Validate Apple Purchase Webhook
Особенность вебхука от Apple в том, что тело полезной нагрузки подписано асимметричным методом шифрования.
Полезная нагрузка
### Как работает проверка асимметричного шифрования?
В заголовке токена указана цепочка сертификатов x509, которые можно валидировать предыдущим сертификатом, а корневой сертификат x509 цепочки валидируется открытым сертификатом Apple из [центра сертификации](https://www.apple.com/certificateauthority/).
Всего сертификатов x509 в заголовке три.
Цепочка генерации подписи хука выглядит следующим образом:
1. Закрытый сертификат, который хранится в закрытом доступе у Apple
2. Открытый сертификат, который можно найти в центре сертификации Apple
3. Корневой сертификат, указанный в заголовке полезной нагрузки
4. Промежуточный сертификат, указанный в заголовке
5. Первый сертификат, указанный в заголовке, который используется для подписи полезной нагрузки
Цепочка валидации выглядит аналогично, только в обратном порядке:
1. Первый сертификат проверяет подпись полезной нагрузки
2. Второй сертификат валидирует первый
3. Корневой сертификат валидирует промежуточный сертификат
4. Корневой сертификат валидируется открытым сертификатом из центра сертификации Apple
### Важно!
Не зная закрытого ключа, с помощью которого был создан открытый сертификат центра сертификации, подделать открытый сертификат от Apple невозможно.
Особенность вебхука от Apple в том, что тело полезной нагрузки подписано асимметричным методом шифрования.
Полезная нагрузка
signedPayload
является сгенерированным токеном JWT.### Как работает проверка асимметричного шифрования?
В заголовке токена указана цепочка сертификатов x509, которые можно валидировать предыдущим сертификатом, а корневой сертификат x509 цепочки валидируется открытым сертификатом Apple из [центра сертификации](https://www.apple.com/certificateauthority/).
Всего сертификатов x509 в заголовке три.
Цепочка генерации подписи хука выглядит следующим образом:
1. Закрытый сертификат, который хранится в закрытом доступе у Apple
2. Открытый сертификат, который можно найти в центре сертификации Apple
3. Корневой сертификат, указанный в заголовке полезной нагрузки
4. Промежуточный сертификат, указанный в заголовке
5. Первый сертификат, указанный в заголовке, который используется для подписи полезной нагрузки
Цепочка валидации выглядит аналогично, только в обратном порядке:
1. Первый сертификат проверяет подпись полезной нагрузки
2. Второй сертификат валидирует первый
3. Корневой сертификат валидирует промежуточный сертификат
4. Корневой сертификат валидируется открытым сертификатом из центра сертификации Apple
### Важно!
Не зная закрытого ключа, с помощью которого был создан открытый сертификат центра сертификации, подделать открытый сертификат от Apple невозможно.
Apple
Apple PKI - Apple
Про коммуникацию между командой разработчиков
После согласования задачи лучше всего выдержать время, так как заказчик (менеджер проекта) может придумать новое условие или вспомнить какие-то упущенные нюансы. А ты, как коммуникатор, должен проверить полученную информацию. Я думаю, для небольших изменений в задачах можно выдерживать промежуток в полчаса-час, а вот для больших задач лучше выдерживать до 4 часов. Задача, как вино, со временем становится только лучше.
#таймменеджмент #коммуникация
После согласования задачи лучше всего выдержать время, так как заказчик (менеджер проекта) может придумать новое условие или вспомнить какие-то упущенные нюансы. А ты, как коммуникатор, должен проверить полученную информацию. Я думаю, для небольших изменений в задачах можно выдерживать промежуток в полчаса-час, а вот для больших задач лучше выдерживать до 4 часов. Задача, как вино, со временем становится только лучше.
#таймменеджмент #коммуникация
из переписки с начинающим
— Файл класс Exception надо называть так, для чего он предназначен, не надо неопределённости Мой Его какая-то. Конкретика нужна в названиях
— В данном случае, это не просто Exception, это ответ на запрос, в ларавель есть уже заготовленный базовый класс для реализации этих исключений
— в текущей задаче, у нас стандартные исключения, которые уже есть.
И так что нам надо сделать?
1. найти места, где нам надо выбросить исключение и ,просто, использовать заготовленные в ларавеле подходящие исключения
2. Чтобы локализовать сообщение об ошибке, мы используем локализацию (<— перечитай —>). для этого выполняем команду
3. в появившийся файлик просто в конце докидываем блок, в котором будем писать сообщения:
теперь нам осnается взять вызывать уже готовое исключение с нашим сообщением из файла локализации
Пример:
— Файл класс Exception надо называть так, для чего он предназначен, не надо неопределённости Мой Его какая-то. Конкретика нужна в названиях
— В данном случае, это не просто Exception, это ответ на запрос, в ларавель есть уже заготовленный базовый класс для реализации этих исключений
HttpException
, если бы нам надо было использовать какой кастомный экцепшен, мы бы отнаследовались от HttpException— в текущей задаче, у нас стандартные исключения, которые уже есть.
BadRequestHttpException
и NotFoundHttpException
(Они есть в примере)И так что нам надо сделать?
1. найти места, где нам надо выбросить исключение и ,просто, использовать заготовленные в ларавеле подходящие исключения
BadRequestHttpException
или NotFoundHttpException
2. Чтобы локализовать сообщение об ошибке, мы используем локализацию (<— перечитай —>). для этого выполняем команду
php artisan lang:publish
, опубликуется директория для локализации.3. в появившийся файлик просто в конце докидываем блок, в котором будем писать сообщения:
'exceptions' => [
'not_found' => ':Entity does not exist',
....
],
теперь нам осnается взять вызывать уже готовое исключение с нашим сообщением из файла локализации
Пример:
throw new NotFoundHttpException(__('validation.exceptions.not_found', ['entity' => 'Event']));
Laravel
Localization - Laravel 12.x - The PHP Framework For Web Artisans
Laravel is a PHP web application framework with expressive, elegant syntax. We’ve already laid the foundation — freeing you to create without sweating the small things.
Сообщение для начинающих бойцов.
Почему нельзя менять предыдущие миграции в laravel?
После того как мы залили изменения в ветку development, происходит деплой (доставка) кода приложения на dev сервер. В ходе этой операции выполняется команда
Кроме того, при изменении существующей миграции существуют случаи, когда такая миграция будет мешать применению последующих миграций, которые были реализованы без учета ваших изменений.
#laravel #migration
Почему нельзя менять предыдущие миграции в laravel?
После того как мы залили изменения в ветку development, происходит деплой (доставка) кода приложения на dev сервер. В ходе этой операции выполняется команда
php artisan migrate
. При выполнении этой команды последовательно исполняются классы миграций, и происходит запись в базу данных об исполнении каждого класса. При последующей доставке приложения на dev сервер, снова выполнится php artisan migrate
, и применятся миграции, которые Laravel не нашёл в базе данных. Кроме того, при изменении существующей миграции существуют случаи, когда такая миграция будет мешать применению последующих миграций, которые были реализованы без учета ваших изменений.
#laravel #migration
Всем привет!
Немного про слои MVC и как они используются в нашем проекте.
Хочу разобрать с вами букву "C".
"С" - Контроллер управляет пользовательским вводом, связывает модель и представление, обрабатывая действия пользователя и обновляя интерфейс в ответ на изменения.
У нас в приложение контроллер разбит на части:
- Request
- Controller
Request - это та часть "C", в которой мы проверяем запрос от пользователя:
— есть ли у нас данные, которые запрашивает пользователь,
— доступны ли эти данные пользователю,
— в каком формате должны быть заданы данные
Controller
— готовит данные для использования в нашем приложение, и передаёт в Модель , в которой уже происходит работа с проверенными и подготовленными данными
— связывается Модель (Service - Repository - Model) c представлением (Recources, Reponse и тд)
PS в сервисе не нужно проверять валидность и доступность запрашиваемых данных для полученного запроса.
Немного про слои MVC и как они используются в нашем проекте.
Хочу разобрать с вами букву "C".
"С" - Контроллер управляет пользовательским вводом, связывает модель и представление, обрабатывая действия пользователя и обновляя интерфейс в ответ на изменения.
У нас в приложение контроллер разбит на части:
- Request
- Controller
Request - это та часть "C", в которой мы проверяем запрос от пользователя:
— есть ли у нас данные, которые запрашивает пользователь,
— доступны ли эти данные пользователю,
— в каком формате должны быть заданы данные
Controller
— готовит данные для использования в нашем приложение, и передаёт в Модель , в которой уже происходит работа с проверенными и подготовленными данными
— связывается Модель (Service - Repository - Model) c представлением (Recources, Reponse и тд)
PS в сервисе не нужно проверять валидность и доступность запрашиваемых данных для полученного запроса.