Заметки дяди Антон
5 subscribers
2 photos
1 file
5 links
работаем и замечаем простые истины
Download Telegram
#ревью
Перед отправкой кода на проверку, проверь свой код как-будто это чужой код. Проверяй не сразу, а на следующий день или после решения другой задачи
#Тестирование

Не забывай делать тесты максимально приближенными к боевым условиям.
С начало может показаться, что это ерунда и можно где-то просто вбить поле от балды или скопировать данные откуда-то переделав одно поле или текущий тест чуть исправить.
Но в дальнейшем это приводит как правило к большой путаницы, и лучшая стратегия в написание тестов:
1 случай = 1 тест
Экшен – это слой, который находится перед сервисом и совершает какое-то полезное действие, например сохранить или обновить данные.

Сервисы на проекте дробятся на экшены, в экшен попадают методы, которые совершают, какие-то действия, и методы, которые помогают сделать нам эти действия, например подготовить данные. Общие методы для экшенов остаются в сервисе, например перобразованиt доларовjq единицы в центы. Экшен обычно наследуется от сервиса, но может и не наследоваться, если экшен уникальный и сервиса не существует.

#ООп #слоистая_архитекрутра #laravel
На память себе
Наш код всегда великолепен, пока не отправишь на ревью
Просто для памяти. первый раз доклад читал
Forwarded from Anton Zabolotnikov
С самого начала проекта у нас было несколько микросервисов и так как у нас было только мобильное приложение - это затрудняло отслеживание запросов, сделанных к нашему серверу.
Для решения этих проблем мы стали использоват Телескоп

В телескопе очень удобно отслеживать что происходит с запросом.
- можно отследить вызванные эндпоинты со всеми входными и выходными параметрами
- увидеть какие джобы тригерятся в процессе выполнения запроса,
- Посмотреть HTTP запросы отправленные в сторонние сервисы, а так же ответы от сторонних API
- Так же можно увидеть какие запросы отправленны в базу данных,
- Телескоп запишит все иключения которые мы получили
- и в итоге увидим что получить фронт после исполнения запроса.

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

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

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

Так же сделали очень удобное изменения БД телескопа, поле content перевели из текстового в JSON, что помогло нам быстрее искать нужную инофрмацию о джобах с помощью SLQ Запросов.

собственно это все наши приключения с телескопом.
Что можно сказать об опыте использования? Телескоп помог нам быстро находить и устранять проблемы с багами. Всё видно, всё удобно.
Forwarded from Anton Zabolotnikov
Обычно на проектах очередь тестируется прямо в одном тесте с запросом в синхронном режиме. или же мы фейкаем джобы, и проверяем была ли вызвана джоба.

На проекте 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());
}


лайкни если понял в чем шедевральность
Написа как-то подавану. сохраню себе

Привет
Смотри:
$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 в том, что тело полезной нагрузки подписано асимметричным методом шифрования.

Полезная нагрузка signedPayload является сгенерированным токеном JWT.

### Как работает проверка асимметричного шифрования?
В заголовке токена указана цепочка сертификатов x509, которые можно валидировать предыдущим сертификатом, а корневой сертификат x509 цепочки валидируется открытым сертификатом Apple из [центра сертификации](https://www.apple.com/certificateauthority/).

Всего сертификатов x509 в заголовке три.

Цепочка генерации подписи хука выглядит следующим образом:
1. Закрытый сертификат, который хранится в закрытом доступе у Apple
2. Открытый сертификат, который можно найти в центре сертификации Apple
3. Корневой сертификат, указанный в заголовке полезной нагрузки
4. Промежуточный сертификат, указанный в заголовке
5. Первый сертификат, указанный в заголовке, который используется для подписи полезной нагрузки

Цепочка валидации выглядит аналогично, только в обратном порядке:
1. Первый сертификат проверяет подпись полезной нагрузки
2. Второй сертификат валидирует первый
3. Корневой сертификат валидирует промежуточный сертификат
4. Корневой сертификат валидируется открытым сертификатом из центра сертификации Apple

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

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

#таймменеджмент #коммуникация
из переписки с начинающим


— Файл класс 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?

После того как мы залили изменения в ветку 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 в сервисе не нужно проверять валидность и доступность запрашиваемых данных для полученного запроса.