Доброго времени суток, я Едифанов Виталий, являюсь CEO своей компании MediaRise, решил собрать распространённые вопросы для интервью PHP Senior разработчика.
Оглавление:
Что такое Идемпотентность. Ключ идемпотентности. Какие HTTP-методы API являются идемпотентные?
DDD: Repository. Collection. Куда поместить логику приложения
Что такое Идемпотентность. Ключ идемпотентности. Какие HTTP-методы API являются идемпотентные?
Что такое Идемпотентность. Ключ идемпотентности. Какие HTTP-методы API являются идемпотентные?
Идемпотентность — это свойство операций в вычислительных системах, которое означает, что многократное применение одной и той же операции к одной и той же системе приведет к тому же результату, что и однократное применение. Иными словами, если операция идемпотентна, её повторное выполнение не изменяет результат после первого выполнения.
Примеры идемпотентных операций:
1. HTTP-методы: GET, PUT, DELETE — все они идемпотентны, поскольку повторный запрос не изменяет состояние сервера.
2. Обнуление значения переменной в программе: присваивание `x = 0` всегда установит значение переменной x в 0, независимо от того, сколько раз эта операция выполняется.
Ключ идемпотентности — это механизм, используемый для обеспечения идемпотентности в распределенных системах и API. Ключ идемпотентности представляет собой уникальный идентификатор, который клиент предоставляет при выполнении запроса. Сервер использует этот ключ для отслеживания уже выполненных операций и предотвращения их повторного выполнения.
Пример использования ключа идемпотентности:
Клиент отправляет запрос на создание ресурса с уникальным ключом идемпотентности.
Сервер сохраняет этот ключ вместе с результатом выполнения запроса.
Если клиент повторно отправляет запрос с тем же ключом, сервер проверяет наличие этого ключа в своей базе данных и возвращает тот же результат, что и при первом запросе, не выполняя операцию повторно.
Таким образом, ключ идемпотентности позволяет избежать дублей операций в случае сетевых сбоев или других непредвиденных ситуаций, когда клиент может не знать, был ли выполнен запрос успешно.
Жизненный цикл laravel
Жизненный цикл laravel
Жизненный цикл запроса в Laravel включает несколько ключевых этапов, которые обеспечивают обработку HTTP-запросов и генерацию ответов. Вот основные этапы жизненного цикла запроса в Laravel:
1. Загрузка Composer Autoload:
- Laravel использует Composer для управления зависимостями. Первым шагом является загрузка всех классов и файлов, необходимых для работы приложения.
2. Инициализация фреймворка:
- Фреймворк Laravel загружается, инициализируя основные компоненты, включая конфигурацию, сервис-провайдеры и другие системные настройки.
3. Запуск HTTP-запроса:
- Приходит HTTP-запрос, и приложение начинает обработку этого запроса.
4. Регистрация сервис-провайдеров:
- Все сервис-провайдеры, зарегистрированные в приложении, инициализируются и регистрируют свои сервисы в контейнере инверсии зависимостей.
5. Загрузка маршрутов:
- Laravel загружает маршруты, которые определены в приложении, и пытается сопоставить текущий запрос с одним из них.
6. Обработка запроса:
- Если маршрут найден, контроллер или замыкание, связанное с этим маршрутом, выполняется. Здесь может происходить вызов моделей, логики обработки данных и т.д.
7. Формирование ответа:
- После выполнения всех необходимых операций формируется HTTP-ответ, который будет отправлен обратно клиенту. Это может быть HTML-страница, JSON-данные или другой тип контента.
8. Отправка ответа клиенту:
- Сформированный ответ отправляется обратно клиенту, завершая жизненный цикл запроса.
Рассмотрим пример жизненного цикла запроса на простом маршруте:
1. Маршрут в `routes/web.php`:
<?php
use Illuminate\Support\Facades\Route;
Route::get('/hello', function () {
return 'Hello, World!';
});
Обработка запроса:
Клиент отправляет GET-запрос на URL `/hello`.
Laravel загружает и инициализирует все необходимые компоненты.
Запрос проходит через стек промежуточного ПО.
Laravel ищет маршрут, соответствующий `/hello`, и находит соответствующее замыкание.
Выполняется замыкание, которое возвращает строку "Hello, World!".
Строка "Hello, World!" упаковывается в HTTP-ответ.
Ответ отправляется обратно клиенту.
Этот пример показывает упрощенный процесс обработки запроса и формирования ответа в Laravel. В реальных приложениях могут быть использованы контроллеры, модели, промежуточное ПО и другие компоненты для реализации более сложной логики.
Kafka
Kafka
Kafka — это распределённая потоковая платформа, разработанная LinkedIn и открытая под лицензией Apache. Она используется для создания высокопроизводительных, масштабируемых систем для передачи данных в реальном времени. Основные компоненты Kafka включают продюсеров (producers), которые отправляют данные в темы (topics), брокеров (brokers), которые хранят данные, и консумеров (consumers), которые читают данные из тем.
Основные концепции Kafka:
Topics: Темы являются логическими каналами, через которые проходят данные. Данные записываются в темы и читаются из них.
Producers: Процедуры отправляют данные в темы.
Consumers: Консумеры читают данные из тем.
Brokers: Брокеры хранят данные и управляют их распределением между продюсерами и консумерами.
Partitions: Темы разделены на разделы (partitions) для обеспечения параллельной обработки данных.
Replication: Данные реплицируются для повышения отказоустойчивости.
Пример использования Kafka на PHP:
1. Установите библиотеку `php-rdkafka`, которая является клиентом для Kafka. Вы можете установить её через Composer:
composer require edenhill/php-rdkafka
2. Пример кода для продюсера, отправляющего сообщения в Kafka:
<?php
// Подключаем автозагрузку Composer
require 'vendor/autoload.php';
// Настройка продюсера
$conf = new RdKafka\Conf();
$conf->set('bootstrap.servers', 'localhost:9092');
$producer = new RdKafka\Producer($conf);
$topic = $producer->newTopic("test");
// Отправка сообщения
$message = "Hello, Kafka!";
$topic->produce(RD_KAFKA_PARTITION_UA, 0, $message);
// Ожидание завершения отправки
$producer->flush(10000);
echo "Сообщение отправлено: $message";
3. Пример кода для консумера, читающего сообщения из Kafka:
// Подключаем автозагрузку Composer
require 'vendor/autoload.php';
// Настройка консумера
$conf = new RdKafka\Conf();
$conf->set('group.id', 'myConsumerGroup');
$conf->set('metadata.broker.list', 'localhost:9092');
$consumer = new RdKafka\KafkaConsumer($conf);
// Подписка на тему
$consumer->subscribe(['test']);
// Чтение сообщений
echo "Ожидание сообщений...\n";
while (true) {
$message = $consumer->consume(120*1000);
switch ($message->err) {
case RD_KAFKA_RESP_ERR_NO_ERROR:
echo "Получено сообщение: " . $message->payload . "\n";
break;
case RD_KAFKA_RESP_ERR__PARTITION_EOF:
echo "Конец раздела, больше нет сообщений\n";
break;
case RD_KAFKA_RESP_ERR__TIMED_OUT:
echo "Таймаут ожидания\n";
break;
default:
echo "Ошибка: " . $message->errstr() . "\n";
break;
}
}
Этот пример показывает, как настроить продюсера и консумера для отправки и получения сообщений с использованием Apache Kafka в PHP. Не забудьте запустить Kafka сервер и создать тему перед запуском этих скриптов.
Redis
Redis
Redis (Remote Dictionary Server) — это высокопроизводительная система хранения данных в оперативной памяти с открытым исходным кодом, которая используется как кэш, брокер сообщений и база данных. Вот основные аспекты его работы:
Архитектура и Устройство Redis
In-Memory Хранение: Redis хранит все данные в оперативной памяти, что обеспечивает очень высокую скорость чтения и записи. Данные могут быть периодически сохраняться на диск для обеспечения надежности (с использованием механизмов snapshotting и журналирования команд).
Однопоточный Дизайн: Redis работает в однопоточном режиме, что упрощает проектирование и устранение состояния гонки, но требует от разработчика тщательной оптимизации команд и использования.
Клиент-Серверная Модель: Redis использует модель клиент-сервер, где клиенты взаимодействуют с сервером Redis через сетевые соединения с использованием текстового протокола Redis.
Типы данных
Redis поддерживает различные структуры данных, что делает его очень гибким:
Строки (Strings): Это базовый и самый простой тип данных в Redis. Строки могут содержать до 512 МБ данных. Их можно использовать для хранения текстов, чисел и бинарных данных.
Списки (Lists): Упорядоченные коллекции строк, которые позволяют добавлять элементы в начало или конец списка. Они полезны для реализации очередей и стеков.
Множества (Sets): Неупорядоченные коллекции уникальных строк. Поддерживают операции над множествами, такие как объединение, пересечение и разность.
Упорядоченные множества (Sorted Sets): Множества, где каждый элемент имеет связанный с ним «вес» (score). Элементы упорядочены по этому весу. Это полезно для создания рейтинговых таблиц и временных шкал.
Хэши (Hashes): Наборы пар ключ‑значение, часто используемые для представления объектов и их свойств. Очень полезны для хранения сущностей с множеством атрибутов.
Битмапы (Bitmaps): Манипулирование отдельными битами в строках, что позволяет эффективно использовать память для хранения больших наборов данных.
Гиперлоглоги (HyperLogLogs): Структура данных для приблизительного подсчета уникальных элементов в наборе, использующая очень мало памяти.
Потоки (Streams): Новый тип данных, введенный в Redis 5.0, который позволяет работать с последовательностями записей. Полезно для обработки событий и сообщений.
Сохранение и Устойчивость данных
Redis предлагает два основных механизма для сохранения данных на диск:
Snapshotting (RDB): Redis периодически создает моментальные снимки (снапшоты) данных и сохраняет их на диск. Это метод сохранения состояния базы данных на определенные моменты времени.
Журналирование команд (AOF): Redis сохраняет каждую операцию записи в журнал (Append Only File). Это позволяет восстановить данные до последнего выполненного изменения.
Репликация и Кластеризация
Репликация: Redis поддерживает асинхронную репликацию, где один сервер (мастер) может передавать данные нескольким серверам (репликам). Это улучшает отказоустойчивость и позволяет масштабировать чтение.
Кластеры: Redis поддерживает распределение данных по нескольким узлам кластера, обеспечивая горизонтальное масштабирование. Кластеры используют шардинг для распределения данных и обеспечения их доступности даже при сбоях отдельных узлов.
Использование и Применения
Redis широко используется в различных сценариях:
Кэширование: Для уменьшения времени отклика приложений за счет хранения часто запрашиваемых данных в памяти.
Сессии: Для хранения пользовательских сессий и токенов аутентификации.
Очереди задач: Для реализации очередей задач с использованием списков.
Публикация/Подписка (Pub/Sub): Для передачи сообщений между приложениями в режиме реального времени.
Аналитика: Для быстрого анализа данных с использованием множеств и упорядоченных множеств.
Redis — это мощный инструмент для работы с данными в реальном времени, который предлагает богатый набор функций и высокую производительность.
Микросервисы. Плюсы и минусы
Микросервисы. Плюсы и минусы
Плюсы микросервисов на PHP
1. Гибкость и масштабируемость:
- Микросервисы позволяют независимо масштабировать отдельные компоненты системы, что помогает более эффективно управлять ресурсами.
2. Изоляция отказов:
- Проблемы в одном микросервисе не влияют на другие части системы, что повышает общую устойчивость приложения.
3. Автономное развитие и развертывание:
- Команды могут работать над разными микросервисами независимо друг от друга, что ускоряет разработку и развертывание новых функциональностей.
4. Технологическая независимость:
- Можно использовать различные технологии и инструменты для каждого микросервиса, что позволяет выбрать наиболее подходящий стек для конкретной задачи.
5. Легкость в понимании и поддержке:
- Небольшие и специализированные микросервисы легче понять и поддерживать, чем монолитное приложение.
Минусы микросервисов на PHP
1. Сложность инфраструктуры:
- Микросервисная архитектура требует более сложной инфраструктуры для управления развертыванием, мониторингом и логированием.
2. Повышенные требования к DevOps:
- Для эффективного управления микросервисами необходимы продвинутые навыки DevOps и использование таких инструментов, как Kubernetes, Docker и CI/CD.
3. Сетевые накладные расходы:
- Взаимодействие между микросервисами происходит по сети, что может увеличивать задержки и требовать дополнительных ресурсов для обеспечения надежности и безопасности.
4. Управление согласованностью данных:
- Распределенные данные требуют сложных механизмов для обеспечения согласованности и целостности данных между микросервисами.
5. Повышенные затраты на разработку и тестирование:
- Разработка микросервисов требует больше времени на проектирование, написание и тестирование, особенно если необходимо обеспечить совместимость между различными микросервисами.
6. Сложности с управлением транзакциями:
- Обеспечение атомарности и согласованности транзакций между микросервисами может быть сложной задачей, требующей применения сложных шаблонов, таких как саги (sagas), Outbox pattern.
Заключение
Микросервисная архитектура на PHP может предоставить значительные преимущества для разработки крупных и сложных систем, особенно в плане масштабируемости и гибкости. Однако она также приносит с собой дополнительные сложности, связанные с управлением инфраструктурой и данными. Решение о переходе на микросервисную архитектуру должно основываться на конкретных потребностях проекта и готовности команды к освоению новых технологий и подходов.
Core Domain in DDD
Core Domain in DDD
Core Domain (ядро домена) в Domain-Driven Design (DDD) — это центральная часть бизнес-домена, которая имеет наибольшее стратегическое значение для организации. Основная цель Core Domain — быть основным источником конкурентного преимущества, определяя уникальные аспекты бизнеса, которые делают его успешным.
В DDD Core Domain выделяется среди других доменов, таких как Supporting Domains (поддерживающие домены) и Generic (Common) Domains (общие домены):
Core Domain: Это ключевая область, на которую организация должна сосредоточить большинство своих усилий, чтобы добиться успеха. Core Domain требует наибольшего внимания и ресурсов для разработки и поддержки, так как именно она определяет уникальные конкурентные преимущества бизнеса. Например, в интернет-магазине Core Domain может включать в себя механизм персонализированных рекомендаций товаров.
Supporting Domain: Эти домены не являются основными источниками конкурентного преимущества, но они поддерживают основной бизнес. Они необходимы для обеспечения бесперебойной работы Core Domain. Например, в том же интернет-магазине поддерживающий домен может включать систему управления складом.
Generic Domain: Это области, которые можно стандартизировать и для которых можно использовать готовые решения. Они не приносят конкурентного преимущества и их лучше всего аутсорсить или использовать сторонние решения. Например, в интернет-магазине к таким доменам могут относиться системы управления платежами.
Основные характеристики Core Domain:
Высокая стратегическая значимость: Определяет уникальные возможности и конкурентные преимущества бизнеса.
Затраты на поддержку: Требует значительных усилий в разработке и поддержке.
Знание и экспертиза: Требует глубокого знания предметной области и тесного взаимодействия с экспертами домена.
Эффективная реализация Core Domain влечет за собой правильное проектирование и архитектуру, что требует тесного сотрудничества между разработчиками и бизнес-экспертами, использование подходов DDD и постоянное совершенствование модели домена.
Что такое сервис в DDD
Что такое сервис в DDD
В контексте предметно-ориентированного проектирования (Domain-Driven Design, DDD), сервис представляет собой объект, который инкапсулирует доменную логику, которая не может быть естественным образом отнесена к отдельной сущности или значению (Value Object). Сервисы используются для реализации операций, которые связаны с несколькими сущностями или требующих сложных вычислений.
Пример на Symfony:
Предположим, у нас есть приложение для управления заказами, где нам нужно вычислить стоимость заказа с учетом различных скидок и налогов.
1. Создание сущностей
Во-первых, определим наши сущности:
Order.php
<?php
namespace App\Entity;
class Order
{
private int $id;
private float $subtotal;
private float $tax;
private float $discount;
public function __construct(float $subtotal, float $tax, float $discount)
{
$this->subtotal = $subtotal;
$this->tax = $tax;
$this->discount = $discount;
}
public function getSubtotal(): float
{
return $this->subtotal;
}
public function getTax(): float
{
return $this->tax;
}
public function getDiscount(): float
{
return $this->discount;
}
}
2. Создание сервиса домена
Теперь создадим доменный сервис, который будет вычислять итоговую стоимость заказа:
OrderCalculatorService.php
<?php
namespace App\Service;
use App\Entity\Order;
class OrderCalculatorService
{
public function calculateTotal(Order $order): float
{
$subtotal = $order->getSubtotal();
$tax = $order->getTax();
$discount = $order->getDiscount();
$total = $subtotal + ($subtotal * $tax / 100) - $discount;
return $total;
}
}
3. Использование сервиса в контроллере
Теперь мы можем использовать наш сервис в контроллере для вычисления итоговой стоимости заказа.
OrderController.php
<?php
namespace App\Controller;
use App\Entity\Order;
use App\Service\OrderCalculatorService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class OrderController extends AbstractController
{
private OrderCalculatorService $orderCalculatorService;
public function __construct(OrderCalculatorService $orderCalculatorService)
{
$this->orderCalculatorService = $orderCalculatorService;
}
/**
* @Route("/order/total", name="order_total")
*/
public function calculateOrderTotal(): Response
{
$order = new Order(100.0, 10.0, 5.0); // Пример данных для заказа
$total = $this->orderCalculatorService->calculateTotal($order);
return new Response('Total order amount: ' . $total);
}
}
Регистрация сервиса в Symfony
Если вы используете автоконфигурацию, Symfony автоматически зарегистрирует сервис `OrderCalculatorService`. Однако, если вы хотите зарегистрировать его вручную, добавьте следующую строку в `config/services.yaml`:
services:
App\Service\OrderCalculatorService: ~
Теперь у вас есть полностью функционирующий пример использования доменного сервиса в Symfony для вычисления итоговой стоимости заказа.
DDD: Entity, Value Object. Различие между ними
DDD: Entity, Value Object. Различие между ними
В предметно-ориентированном проектировании (Domain-Driven Design, DDD) сущности (Entity) и объекты-значения (Value Object) играют ключевые роли.
Отличия Entity и Value Object
- Entity (Сущность):
- Имеет уникальный идентификатор (ID), который отличает одну сущность от другой.
- Сохраняет свою идентичность на протяжении всего жизненного цикла, даже если ее свойства изменяются.
- Пример: пользователь, заказ, продукт.
- Value Object (Объект-значение):
- Не имеет уникального идентификатора.
- Считается неизменяемым: любое изменение создает новый объект.
- Сравнивается по значению, а не по идентификатору.
- Пример: адрес, деньги, координаты.
Пример на Symfony
1. Создание Entity
Предположим, у нас есть сущность `User` с уникальным идентификатором и некоторыми полями.
User.php
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity()
*/
class User
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
*/
private int $id;
/**
* @ORM\Column(type="string", length=100)
*/
private string $name;
/**
* @ORM\Column(type="string", length=100)
*/
private string $email;
public function __construct(string $name, string $email)
{
$this->name = $name;
$this->email = $email;
}
public function getId(): int
{
return $this->id;
}
public function getName(): string
{
return $this->name;
}
public function getEmail(): string
{
return $this->email;
}
public function setName(string $name): void
{
$this->name = $name;
}
public function setEmail(string $email): void
{
$this->email = $email;
}
}
2. Создание Value Object
Теперь создадим объект-значение для адреса.
Address.php
<?php
namespace App\ValueObject;
class Address
{
private string $street;
private string $city;
private string $postalCode;
public function __construct(string $street, string $city, string $postalCode)
{
$this->street = $street;
$this->city = $city;
$this->postalCode = $postalCode;
}
public function getStreet(): string
{
return $this->street;
}
public function getCity(): string
{
return $this->city;
}
public function getPostalCode(): string
{
return $this->postalCode;
}
public function equals(Address $address): bool
{
return $this->street === $address->getStreet() &&
$this->city === $address->getCity() &&
$this->postalCode === $address->getPostalCode();
}
}
3. Использование Value Object в Entity
Теперь добавим адрес к пользователю.
User.php (обновленный)
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use App\ValueObject\Address;
/**
* @ORM\Entity()
*/
class User
{
// ...
/**
* @ORM\Embedded(class="App\ValueObject\Address")
*/
private Address $address;
public function __construct(string $name, string $email, Address $address)
{
$this->name = $name;
$this->email = $email;
$this->address = $address;
}
// ...
public function getAddress(): Address
{
return $this->address;
}
public function setAddress(Address $address): void
{
$this->address = $address;
}
}
Конфигурация Doctrine
Необходимо также настроить Doctrine для работы с объектами-значениями.
config/packages/doctrine.yaml
doctrine:
orm:
mappings:
App:
is_bundle: false
type: annotation
dir: '%kernel.project_dir%/src/Entity'
prefix: 'App\Entity'
alias: App
types:
address:
class: App\ValueObject\Address
С этим примером вы теперь понимаете, как реализовать сущности и объекты-значения в Symfony с использованием DDD. Сущности представляют собой объекты с уникальной идентификацией и изменяемыми свойствами, тогда как объекты-значения представляют собой неизменяемые объекты, сравниваемые по значению.
DDD: Repository. Collection. Куда поместить логику приложения
DDD: Repository. Collection. Куда поместить логику приложения
В Domain-Driven Design (DDD) на PHP репозиторий и коллекция выполняют важные роли в управлении доменными объектами и бизнес-логикой. Давайте рассмотрим их более подробно:
Репозиторий (Repository)
Репозиторий является паттерном для доступа к данным, который обеспечивает абстракцию над источниками данных (например, база данных). Репозиторий позволяет извлекать и сохранять доменные объекты, скрывая детали реализации хранилища данных.
Основные задачи репозитория:
Извлечение доменных объектов: предоставление методов для получения объектов на основе различных критериев.
Сохранение доменных объектов: предоставление методов для сохранения изменений в доменных объектах.
Удаление доменных объектов: предоставление методов для удаления объектов.
Пример интерфейса репозитория:
<?php
interface UserRepository
{
public function findById(int $id): ?User;
public function findByEmail(string $email): ?User;
public function save(User $user): void;
public function delete(User $user): void;
}
Коллекция (Collection)
Коллекция – это набор объектов, который может использоваться для управления группой объектов как единым целым. В контексте DDD коллекции часто используются для управления связями между агрегатами.
Основные задачи коллекции:
Управление набором объектов: добавление, удаление, итерация по объектам.
Предоставление методов для работы с группой объектов: методы для фильтрации, сортировки и других операций над группой объектов.
Пример коллекции:
<?php
class UserCollection implements \IteratorAggregate
{
private array $users = [];
public function add(User $user): void
{
$this->users[] = $user;
}
public function remove(User $user): void
{
$this->users = array_filter($this->users, fn($u) => $u !== $user);
}
public function getIterator(): \ArrayIterator
{
return new \ArrayIterator($this->users);
}
}
Куда поместить бизнес-логику
Бизнес-логика в DDD должна быть сосредоточена в доменной модели, то есть в сущностях, значимых объектах и агрегатах. Она не должна находиться в репозиториях или сервисах данных. Основные места для бизнес-логики:
Сущности (Entities): объекты с уникальной идентичностью, которые изменяются с течением времени.
Объекты значений (Value Objects): объекты без уникальной идентичности, которые неизменяемы.
Агрегаты (Aggregates): группы связанных объектов, которые рассматриваются как единое целое для целей изменения данных.
Доменные сервисы (Domain Services): объекты, содержащие бизнес-логику, которая не подходит ни для сущностей, ни для объектов значений.
Пример сущности с бизнес-логикой:
<?php
class User
{
private int $id;
private string $email;
private string $passwordHash;
public function __construct(int $id, string $email, string $passwordHash)
{
$this->id = $id;
$this->email = $email;
$this->passwordHash = $passwordHash;
}
public function changeEmail(string $newEmail): void
{
if (!filter_var($newEmail, FILTER_VALIDATE_EMAIL)) {
throw new \InvalidArgumentException("Invalid email format");
}
$this->email = $newEmail;
}
public function checkPassword(string $password): bool
{
return password_verify($password, $this->passwordHash);
}
}
Таким образом, бизнес-логика размещается внутри доменных моделей, а репозитории и коллекции используются для управления и хранения этих моделей.
Event Sourcing
Event Sourcing
Event Sourcing (от англ. "источник событий") — это шаблон проектирования программного обеспечения, при котором изменения состояния системы представляются в виде последовательности событий. Основная идея заключается в том, что вместо хранения текущего состояния системы в базе данных, все изменения состояния сохраняются как последовательность неизменяемых событий. Текущее состояние системы в любой момент времени можно получить, применяя эти события последовательно.
Вот основные концепты Event Sourcing:
События: Все изменения состояния системы записываются как события. Событие представляет собой неизменяемую запись, которая описывает изменение состояния.
Хранение событий: События хранятся в специальном хранилище событий (Event Store). Это может быть база данных, специально предназначенная для хранения событий, или обычная база данных, адаптированная для этих целей.
Восстановление состояния: Для получения текущего состояния системы события читаются и применяются последовательно. Это называется "реплеем" (replay) событий.
CQRS (Command Query Responsibility Segregation): Event Sourcing часто используется вместе с CQRS, шаблоном, разделяющим операции на чтение и запись данных. Команды (commands) изменяют состояние системы, создавая новые события, а запросы (queries) читают состояние системы.
Преимущества Event Sourcing включают:
Возможность воспроизведения всех изменений состояния системы для аудита или восстановления.
Простота реализации функций, связанных с временными аспектами (например, просмотр состояния системы на конкретный момент времени).
Улучшенная масштабируемость за счет разделения операций на чтение и запись.
Недостатки могут включать сложность реализации и необходимость управления большим объемом событий.
Виды тестирования
Виды тестирования
В PHP, как и в любом другом языке программирования, существует множество видов тестирования, направленных на проверку различных аспектов кода и обеспечения его качества. Вот основные виды тестирования, применяемые в PHP:
1. Модульное тестирование (Unit Testing):
- Проверяет отдельные модули или функции программы на корректность их работы.
- В PHP для этого часто используются библиотеки, такие как PHPUnit, Codeception или PHPSpec.
2. Интеграционное тестирование (Integration Testing):
- Проверяет, как различные модули или компоненты системы работают вместе.
- Для интеграционного тестирования можно также использовать Codeception или Behat.
3. Функциональное тестирование (Functional Testing):
- Проверяет функциональность системы с точки зрения пользователя.
- Инструменты: Behat, Codeception.
4. Тестирование пользовательского интерфейса (UI Testing):
- Проверяет интерфейс пользователя на корректность отображения и работы.
- Инструменты: Selenium, Codeception с модулем WebDriver, Laravel Dusk.
5. Тестирование производительности (Performance Testing):
- Проверяет производительность приложения под различными нагрузками.
- Инструменты: Apache JMeter, Gatling, PHPBench.
6. Нагрузочное тестирование (Load Testing):
- Проверяет, как система справляется с высокими нагрузками и каковы её пределы.
- Инструменты: Apache JMeter, Gatling.
7. Тестирование безопасности (Security Testing):
- Проверяет приложение на наличие уязвимостей.
- Инструменты: OWASP ZAP, phpstan, Psalm.
8. Тестирование совместимости (Compatibility Testing):
- Проверяет, как приложение работает в различных окружениях, браузерах, операционных системах и устройствах.
- Инструменты: BrowserStack, Sauce Labs.
9. Приемочное тестирование (Acceptance Testing):
- Проверяет, соответствует ли приложение требованиям и ожиданиям пользователя.
- Инструменты: Behat, Codeception.
10. Тестирование регрессионное (Regression Testing):
- Проверяет, что новые изменения в коде не нарушили существующую функциональность.
- Инструменты: PHPUnit, Codeception.
Каждый из этих видов тестирования имеет свою цель и область применения, и использование их комбинации помогает обеспечить высокое качество и надежность PHP-приложения.
Создание библиотеки на PHP
Создание библиотеки на PHP (Туториал)
Создание библиотеки в PHP включает несколько ключевых шагов. Вот пошаговое руководство, чтобы помочь вам начать:
1. Определите Цель и Функционал Библиотеки
Прежде чем писать код, четко определите, какие задачи будет выполнять ваша библиотека и какие функции она должна содержать.
2. Создайте Структуру Проекта
Создайте структуру директорий для вашей библиотеки. Это может выглядеть следующим образом:
my-library/
├── src/
│ ├── MyClass.php
├── tests/
│ ├── MyClassTest.php
├── vendor/
├── composer.json
├── README.md
3. Напишите Код Библиотеки
В директории `src/` создайте файлы с кодом вашей библиотеки.
Пример файла `src/MyClass.php`:
<?php
namespace MyLibrary;
class MyClass {
public function sayHello() {
return "Hello, World!";
}
}
4. Настройте Composer
Composer — это стандартный менеджер пакетов для PHP, который упростит управление зависимостями и автозагрузку классов.
Создайте файл `composer.json` в корне вашего проекта:
{
"name": "your-username/my-library",
"description": "A brief description of your library",
"type": "library",
"autoload": {
"psr-4": {
"MyLibrary\\": "src/"
}
},
"require": {
"php": ">=7.4"
},
"require-dev": {
"phpunit/phpunit": "^9"
}
}
5. Установите Зависимости
Выполните команду `composer install`, чтобы установить зависимости и создать автозагрузчик.
composer install
6. Напишите Тесты
В директории `tests/` создайте тесты для вашей библиотеки. Используйте PHPUnit для тестирования.
Пример файла `tests/MyClassTest.php`:
<?php
use PHPUnit\Framework\TestCase;
use MyLibrary\MyClass;
class MyClassTest extends TestCase {
public function testSayHello() {
$myClass = new MyClass();
$this->assertEquals("Hello, World!", $myClass->sayHello());
}
}
7. Запустите Тесты
Запустите тесты, чтобы убедиться, что все работает корректно:
vendor/bin/phpunit tests
8. Напишите Документацию
Создайте файл `README.md` с инструкциями по установке и использованию вашей библиотеки.
Пример:
# My Library
## Installation
```bash
composer require your-username/my-library
```
## Usage
```php
use MyLibrary\MyClass;
$myClass = new MyClass();
echo $myClass->sayHello();
```
9. Опубликуйте Библиотеку
Если вы хотите, чтобы ваша библиотека была доступна для всех, опубликуйте ее на Packagist.
Зарегистрируйтесь на Packagist.
Создайте репозиторий на GitHub и загрузите туда ваш код.
Добавьте ваш репозиторий на Packagist.
Дополнительные Рекомендации
Следуйте стандартам кодирования (например, PSR-12).
Используйте системы CI/CD для автоматического тестирования и развертывания.
Регулярно обновляйте библиотеку и следите за зависимостями.
Как добиться отказоустойчивости между Service A и Service B
Как добиться отказоустойчивости между Service A и Service B
Ответ: Применить шаблон Outbox
Pattern Outbox (паттерн «Исходящий ящик») - это архитектурный шаблон, используемый для обеспечения гарантированной доставки сообщений из одного компонента системы в другой. Основная цель этого паттерна заключается в обеспечении надежной и упорядоченной передачи данных между сервисами, особенно в распределенных системах.
Основные идеи паттерна Outbox:
1. Запись в таблицу исходящих сообщений:
Вместо того чтобы отправлять сообщение напрямую во внешний компонент (например, в очередь сообщений), сервис сначала записывает сообщение в специальную таблицу исходящих сообщений в своей базе данных. Это позволяет гарантировать, что сообщение не будет потеряно даже в случае сбоя системы.
2. Транзакционная консистентность:
Запись в таблицу исходящих сообщений происходит в той же транзакции, что и изменение данных в основной таблице. Это обеспечивает согласованность данных: либо оба изменения происходят, либо не происходит ни одно.
3. Процессор исходящих сообщений:
Специальный компонент (чаще всего это отдельный сервис или фоновый процесс) периодически считывает сообщения из таблицы исходящих сообщений и отправляет их в целевой компонент (например, в очередь сообщений или другой сервис). После успешной отправки сообщение помечается как отправленное или удаляется из таблицы.
4. Обработка дубликатов:
В случае сбоев возможны повторные отправки одного и того же сообщения. Поэтому целевой компонент должен быть идемпотентным (т.е. корректно обрабатывать повторные поступления одного и того же сообщения).
Пример работы:
Представим, что у нас есть два микросервиса: сервис заказов (Order Service) и сервис уведомлений (Notification Service).
Клиент создает заказ через Order Service.
Order Service записывает данные о новом заказе в свою базу данных и в той же транзакции добавляет запись в таблицу исходящих сообщений о необходимости уведомить пользователя о создании заказа.
Процессор исходящих сообщений Order Service периодически проверяет таблицу исходящих сообщений, видит новое сообщение и отправляет его в Notification Service.
Notification Service получает сообщение и отправляет уведомление пользователю.
После успешной отправки уведомления, процессор исходящих сообщений Order Service помечает сообщение как отправленное или удаляет его из таблицы.
Преимущества:
Гарантированная доставка: Паттерн обеспечивает надежную доставку сообщений даже в случае сбоев системы.
Согласованность данных: Транзакционная запись в таблицу исходящих сообщений обеспечивает консистентность данных.
Простота восстановления после сбоя: В случае сбоя система может просто перечитать сообщения из таблицы исходящих сообщений и повторить попытку отправки.
Недостатки:
Сложность реализации: Необходимо реализовать дополнительный компонент (процессор исходящих сообщений) и обеспечить его надежную работу.
Увеличение задержек: Между записью сообщения и его фактической отправкой может быть задержка, зависящая от частоты опроса таблицы исходящих сообщений.
Заключение:
Pattern Outbox является мощным инструментом для обеспечения надежной передачи данных в распределенных системах. Он широко используется в микросервисной архитектуре для обеспечения согласованности и надежности обмена сообщениями между сервисами.
Проведи рефакторинг данного кода на PHP. Code Review
Проведи рефакторинг данного кода на PHP. Code Review
Рефакторинг кода поможет улучшить его читаемость, поддерживаемость и масштабируемость.
Вот подробное объяснение изменений и сам рефакторинг кода:
Основные изменения:
Внедрение зависимостей (Dependency Injection): Репозитории и сервисы теперь передаются через конструктор. Это делает код более гибким и тестируемым.
Интерфейсы: Использование интерфейсов для уменьшения связности кода и упрощения замены реализаций.
Принцип единственной ответственности: Каждый класс выполняет только одну функцию (а именно у нас одна причина на изменение кода), что соответствует принципу единственной ответственности (SRP).
Улучшение читаемости: Код стал более организованным и поддерживаемым благодаря разделению ответственности.
Рефакторинг:
<?php
// Интерфейсы для репозиториев и сервисов
interface ItemRepositoryInterface
{
public function get($itemId);
public function save($item);
}
interface OrderRepositoryInterface
{
public function save($order);
}
interface SmsServiceInterface
{
public function send($message);
}
// Реализация интерфейсов
class MySqlItemRepository implements ItemRepositoryInterface
{
public function get($itemId)
{
// Реализация получения товара из MySQL
}
public function save($item)
{
// Реализация сохранения товара в MySQL
}
}
class PgOrderRepository implements OrderRepositoryInterface
{
public function save($order)
{
// Реализация сохранения заказа в PostgreSQL
}
}
class SimpleSmsService implements SmsServiceInterface
{
private $login;
private $password;
public function __construct($login, $password)
{
$this->login = $login;
$this->password = $password;
}
public function send($message)
{
// Реализация отправки SMS
}
}
// Основной контроллер
class OrderController
{
private $itemRepository;
private $orderRepository;
private $smsService;
public function __construct(ItemRepositoryInterface $itemRepository, OrderRepositoryInterface $orderRepository, SmsServiceInterface $smsService)
{
$this->itemRepository = $itemRepository;
$this->orderRepository = $orderRepository;
$this->smsService = $smsService;
}
public function processOrder(Request $request)
{
$itemId = $request->get('item_id');
$quantity = $request->get('quantity');
$item = $this->itemRepository->get($itemId);
$item->sell($quantity);
$this->itemRepository->save($item);
$order = new Order('id-abc', $request->user()->id, $itemId, $quantity);
$this->orderRepository->save($order);
$this->smsService->send('An order placed');
return 'Ok';
}
}
// Пример использования
$request = new Request(); // Предполагаем, что $request является экземпляром класса Request
$itemRepository = new MySqlItemRepository();
$orderRepository = new PgOrderRepository();
$smsService = new SimpleSmsService('login', 'pass');
$orderController = new OrderController($itemRepository, $orderRepository, $smsService);
$response = $orderController->processOrder($request);
echo $response;
Пояснения:
- Интерфейсы: Созданы интерфейсы для репозиториев и сервисов (`ItemRepositoryInterface`, `OrderRepositoryInterface`, `SmsServiceInterface`), чтобы уменьшить зависимость от конкретных реализаций и упростить замену одного репозитория или сервиса на другой.
- Реализации интерфейсов: Реализованы классы `MySqlItemRepository`, `PgOrderRepository`, `SimpleSmsService`, которые реализуют соответствующие интерфейсы.
- Контроллер: Контроллер `OrderController` теперь принимает зависимости через конструктор, что позволяет легко заменять зависимости при тестировании или изменении логики.
- Пример использования: Показан пример создания экземпляров классов и использования контроллера.
Также логику из контроллера переносим в сервисный слой (создаем например OrderService)
SELECT FOR UPDATE
SELECT FOR UPDATE
`SELECT FOR UPDATE` — это SQL-запрос, который используется для получения блокировки на строки, выбранные из базы данных, для предотвращения их изменения другими транзакциями до завершения текущей транзакции. Это полезно в ситуациях, когда нужно убедиться, что данные не изменяются другими пользователями во время выполнения определенных операций, таких как обновление или удаление записей.
Пример использования `SELECT FOR UPDATE`
Рассмотрим таблицу `accounts`:
CREATE TABLE accounts (
id INT PRIMARY KEY,
balance DECIMAL
);
Если нужно перевести деньги между двумя счетами, необходимо убедиться, что баланс обоих счетов не изменится другими транзакциями во время выполнения операции.
Пример 1: Перевод денег между счетами
BEGIN;
-- Блокировка строк для аккаунтов с id 1 и 2
SELECT balance FROM accounts WHERE id = 1 FOR UPDATE;
SELECT balance FROM accounts WHERE id = 2 FOR UPDATE;
-- Получение текущих балансов (предположим, они хранятся в переменных)
SET @balance1 = (SELECT balance FROM accounts WHERE id = 1);
SET @balance2 = (SELECT balance FROM accounts WHERE id = 2);
-- Перевод 100 единиц с аккаунта 1 на аккаунт 2
UPDATE accounts SET balance = @balance1 - 100 WHERE id = 1;
UPDATE accounts SET balance = @balance2 + 100 WHERE id = 2;
COMMIT;
В этом примере транзакция начинает с получения блокировки на строки для счетов с id 1 и 2. Это гарантирует, что никто другой не сможет изменить эти строки, пока транзакция не завершится. После этого выполняются операции обновления балансов, и транзакция завершается.
Особенности и Поведение
1. Блокировка только на чтение: `SELECT FOR UPDATE` блокирует строки только на изменение, т.е. другие транзакции могут читать эти строки, но не могут их изменять или удалять.
2. Продолжительность блокировки: Блокировка удерживается до завершения текущей транзакции (commit или rollback).
3. Совместимость с другими блокировками: `SELECT FOR UPDATE` несовместим с другими блокировками на те же строки. Если другая транзакция пытается заблокировать те же строки, она будет ждать завершения текущей транзакции.
4. Оптимизация производительности: Использование `SELECT FOR UPDATE` следует тщательно продумывать, так как оно может привести к проблемам с производительностью, особенно если транзакция длится долго и блокирует много строк.
Расширенные возможности
В некоторых СУБД, таких как PostgreSQL, `SELECT FOR UPDATE` может быть дополнен разными режимами блокировки:
FOR NO KEY UPDATE: Блокирует строки для обновления, но позволяет вставку или удаление в индексе.
FOR SHARE: Разрешает другим транзакциям чтение строк и установку `FOR SHARE` блокировки, но запрещает `FOR UPDATE` и другие более строгие блокировки.
FOR KEY SHARE: Позволяет другим транзакциям ставить `FOR SHARE` или `FOR KEY SHARE` блокировки, но не позволяет `FOR UPDATE` или `FOR NO KEY UPDATE`.
SELECT FOR UPDATE — мощный инструмент для обеспечения целостности данных в многопользовательских системах. Однако его использование должно быть сбалансировано с учетом возможного влияния на производительность и блокировку других операций в базе данных.
Заключение
Спасибо за внимание! А если вам скучно, можете с друзьями поиграть в мой онлайн шутер (Alpha)
Комментарии (42)
AlexeichD
12.07.2024 08:56+1И почем нынче специалист, кто знает все ответы?)
ChizhM
12.07.2024 08:56Как хорошо, что я этого всего не знаю... и 20 лет разрабатываю сайты сми и интернет-магазины на php. :-)
kozlov_de
12.07.2024 08:56+1Мой рефакторинг
<?php use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; use App\Http\Controllers\Controller; use App\Repositories\ItemRepository; use App\Repositories\OrderRepository; use App\Services\NotificationService; use Illuminate\Http\JsonResponse; class OrderController extends Controller { public function __construct( private ItemRepository $itemRepository, private OrderRepository $orderRepository, private NotificationService $notificationService ) {} public function processOrder(Request $request): JsonResponse { $validated = $request->validate([ 'item_id' => 'required|uuid', 'quantity' => 'required|integer|min:1', ]); $userId = $request->user()->id; $item = $this->itemRepository->findOrFail($validated['item_id']); return match(true) { $item === null => response()->json(['error' => 'Item not found'], 404), $item->stock < $validated['quantity'] => response()->json(['error' => 'Not enough items in stock'], 400), default => $this->processValidOrder($item, $userId, $validated['quantity']) }; } private function processValidOrder($item, $userId, $quantity): JsonResponse { try { DB::beginTransaction(); $this->itemRepository->decrementStock($item->id, $quantity); $order = $this->orderRepository->createOrder($userId, $item->id, $quantity); $this->notificationService->notify($userId, 'Order placed successfully'); DB::commit(); return response()->json([ 'order_id' => $order->id, 'message' => 'Order processed successfully' ], 200); } catch (\Exception $e) { DB::rollBack(); return response()->json(['error' => 'An error occurred while processing the order'], 500); } } }
© claude-3-5-sonnet-20240620 под моим присмотром.
Написано в стиле .net
А вот ревью на ваш рефракторинг
Ваш код уже демонстрирует хорошие практики, такие как использование интерфейсов и внедрение зависимостей. Тем не менее, вот несколько предложений по улучшению:
Типизация:
Добавьте строгую типизацию для параметров и возвращаемых значений методов.
Обработка ошибок:
Добавьте обработку исключений в методе processOrder.
Валидация входных данных:
Реализуйте валидацию входных данных в начале метода processOrder.
Использование современных возможностей PHP:
Используйте типизированные свойства класса (PHP 7.4+).
Применяйте return type declarations.
Именование:
Переименуйте метод sell в Item на что-то более описательное, например, decreaseStock.
Безопасность:
Не передавайте напрямую логин и пароль в конструктор SimpleSmsService. Лучше использовать конфигурацию или переменные окружения.
Ответ контроллера:
Вместо простого 'Ok' возвращайте более информативный ответ, например, JSON с деталями заказа.
Генерация ID заказа:
Вместо хардкода 'id-abc', используйте генерацию уникального ID.
MediaRise Автор
12.07.2024 08:56логику бы также из контроллера вынести
FanatPHP
12.07.2024 08:56Тут не худо бы в зеркало посмотреться :) Вся эта кухня
$item = $this->itemRepository->get($itemId); $item->sell($quantity); $this->itemRepository->save($item); $order = new Order('id-abc', $request->user()->id, $itemId, $quantity); $this->orderRepository->save($order);
тоже явно не для контроллера.
MediaRise Автор
12.07.2024 08:56В статье описано, что логику в сервисный слой вынести необходимо.
FanatPHP
12.07.2024 08:56+2В статье написано, но в коде-то нет. Если уж вы сами не вынесли, то тогда и комментатора за это не упрекайте :)
MediaRise Автор
12.07.2024 08:56Во первых я написал, это есть в статье
Во вторых не упрекаю, а дополняю своим комментарием
В третих, не вам указывать что мне делатьFanatPHP
12.07.2024 08:56+2Вы совершенно зря обижаетесь.
Во-первых, "в статье" написано только на словах, "Также логику из контроллера переносим в сервисный слой (создаем например OrderService)", но в предложенном варианте отрефакторенного кода этот сервис не реализован. Кандидата за такой "рефакторинг" на словах, "Вот код! Ну там еще надо будет логику в сервис вынести" вы же сами первый попросите на выход.А во-вторых, кому ещё указывать-то, как не мне? Я пришел на Хабр, читаю статью, пишу комментарий (причем по грамматическим ошибкам культурно написал в личку, но вы даже не отреагировали). Комментарии не всегда бывают комплементарными. Если хотите получать только хвалебные отзывы на свои статьи, то и не размещайте их на Хабре. Пишите в личный блог тогда, и дайте доступ только своим друзьям.
Не кипятитесь пожалуйста. Я прекрасно понимаю, как обидно, когда статью, в которую ты вложил столько труда, принимают не слишком восторженно или находят в ней кучу ляпов. Но выливая свое раздражение на публику вы только усугубите ситуацию.
MediaRise Автор
12.07.2024 08:56Видимо аватарка наложило впечатление))
Спасибо за ответ, не обижаюсь!
FanatPHP
12.07.2024 08:56Вполне ожидаемо для AI, лишние
ушипальцы торчат отовсюду. Контроллер по уши залез в бизнес-логику, findOrFail возвращает null, критическая ошибка не логируется и не рендерится, соединение с БД берется из воздуха непонятно какоеkozlov_de
12.07.2024 08:56Всем спасибо за замечания
Тогда так
<?php namespace App\Http\Controllers; use App\Http\Requests\CreateOrderRequest; use App\Services\OrderService; use Illuminate\Http\JsonResponse; class OrderController extends Controller { public function __construct( private OrderService $orderService ) {} public function store(CreateOrderRequest $request): JsonResponse { $order = $this->orderService->createOrder( $request->user()->id, $request->validated('item_id'), $request->validated('quantity') ); return response()->json([ 'order_id' => $order->id, 'message' => 'Order processed successfully' ], 201); } } namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; class CreateOrderRequest extends FormRequest { public function rules(): array { return [ 'item_id' => 'required|uuid', 'quantity' => 'required|integer|min:1', ]; } } namespace App\Services; use App\Repositories\ItemRepository; use App\Repositories\OrderRepository; use App\Exceptions\InsufficientStockException; use App\Exceptions\ItemNotFoundException; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; class OrderService { public function __construct( private ItemRepository $itemRepository, private OrderRepository $orderRepository, private NotificationService $notificationService ) {} public function createOrder(string $userId, string $itemId, int $quantity) { try { return DB::transaction(function () use ($userId, $itemId, $quantity) { $item = $this->itemRepository->findOrFail($itemId); if ($item->stock < $quantity) { throw new InsufficientStockException("Not enough items in stock"); } $this->itemRepository->decrementStock($item->id, $quantity); $order = $this->orderRepository->createOrder($userId, $item->id, $quantity); $this->notificationService->notify($userId, 'Order placed successfully'); Log::info('Order processed', ['order_id' => $order->id, 'user_id' => $userId]); return $order; }); } catch (\Exception $e) { Log::error('Order processing failed', [ 'error' => $e->getMessage(), 'user_id' => $userId, 'item_id' => $itemId, 'quantity' => $quantity ]); throw $e; } } } namespace App\Exceptions; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; use Throwable; class Handler extends ExceptionHandler { public function render($request, Throwable $e) { if ($e instanceof ItemNotFoundException) { return response()->json(['error' => $e->getMessage()], 404); } if ($e instanceof InsufficientStockException) { return response()->json(['error' => $e->getMessage()], 400); } return parent::render($request, $e); } }
Кстати, у автора тоже бизнес логика в контроллере
Так вот плавно 25 строк кода превращается в 125
Но красиво
FanatPHP
12.07.2024 08:56Да, это уже сильно лучше. Кода действительно стало больше, но он разнесен по разным модулям. А сам контроллер похудел и занимается ровно тем, чем должен. Придраться остается если только по мелочи.
соединение с БД для транзакции всё так же берется из воздуха (чем резко диссонирует с остальными зависимостями)
насколько я могу судить, по задаче товары и заказы вообще-то лежат в разных СУБД (но это совсем уже придирка, понятно что бедняжке GPT это совсем не осилить)
нотификакцию я бы пожалуй вынес из транзакции. Отменять успешный заказ только потому что лег гейт СМС как-то глупо.
собственно наворачивать еще один try-catch в дополнение к тому, который и так есть DB::transaction() я не вижу смысла. Уж чего-чего, а логировать ошибки Ларавель умеет и сам.
kozlov_de
12.07.2024 08:56Ещё вариант попроще и завязал )
Задолбался это делать на телефоне
Вся моя работа это сплошной рефакторинг. Правда, на c#. PHP только читаю и то не особо.
распределённая транзакция...
<?php namespace App\Http\Controllers; use App\Http\Requests\CreateOrderRequest; use App\Services\OrderService; use Illuminate\Http\JsonResponse; use Illuminate\Support\Facades\Log; class OrderController extends Controller { public function __construct(private OrderService $orderService) {} public function store(CreateOrderRequest $request): JsonResponse { try { $order = $this->orderService->createOrder( $request->user()->id, $request->input('item_id'), $request->input('quantity') ); return response()->json(['order_id' => $order->id], 201); } catch (\Exception $e) { Log::error('Failed to create order: ' . $e->getMessage(), [ 'user_id' => $request->user()->id, 'item_id' => $request->input('item_id'), 'quantity' => $request->input('quantity'), ]); throw $e; } } } namespace App\Services; use App\Repositories\ItemRepository; use App\Repositories\OrderRepository; use App\Exceptions\InsufficientStockException; use Illuminate\Support\Facades\DB; use Netsells\DistributedTransactions\Facades\DistributedTransaction; class OrderService { public function __construct( private ItemRepository $items, private OrderRepository $orders, private NotificationService $notifications, private DB $itemsDb, private DB $ordersDb ) {} public function createOrder(string $userId, string $itemId, int $quantity) { $item = $this->items->findOrFail($itemId, $this->itemsDb); throw_if($item->stock < $quantity, InsufficientStockException::class); return DistributedTransaction::run([$this->itemsDb, $this->ordersDb], function () use ($userId, $itemId, $quantity, $item) { $this->items->decrementStock($item->id, $quantity, $this->itemsDb); $orderId = $this->orders->create($userId, $itemId, $quantity, $this->ordersDb); $this->notifications->notify($userId, 'Order placed successfully'); return $orderId; }); } } namespace App\Repositories; use Illuminate\Support\Facades\DB; class ItemRepository { public function findOrFail(string $itemId, DB $db) { return $db->table('items')->where('id', $itemId)->firstOrFail(); } public function decrementStock(string $itemId, int $quantity, DB $db) { $db->table('items')->where('id', $itemId)->decrement('stock', $quantity); } } namespace App\Repositories; use Illuminate\Support\Facades\DB; class OrderRepository { public function create(string $userId, string $itemId, int $quantity, DB $db) { return $db->table('orders')->insertGetId([ 'user_id' => $userId, 'item_id' => $itemId, 'quantity' => $quantity, ]); } } namespace App\Exceptions; use Exception; use Illuminate\Support\Facades\Log; class InsufficientStockException extends Exception { public function render($request) { Log::warning('Insufficient stock: ' . $this->getMessage(), [ 'user_id' => $request->user()->id, 'item_id' => $request->input('item_id'), 'quantity' => $request->input('quantity'), ]); return response()->json(['error' => 'Insufficient stock'], 400); } }
kozlov_de
12.07.2024 08:56Вот так код и раздувается в 5-7 раз
Задумаешься прежде чем рефакторить - а надо ли это в данном случае...
Улучшение 1: Разделение ответственности (Separation of Concerns)
Стоимость улучшения 1: 20 строк кода
Описание: Вынесение логики создания заказа из контроллера в отдельный сервисOrderService
позволяет разделить ответственность между классами. Контроллер теперь отвечает только за обработку HTTP-запросов и возврат ответов, а сервис - за бизнес-логику создания заказа. Это делает код более модульным, облегчает тестирование и упрощает внесение изменений в будущем.Улучшение 2: Внедрение зависимостей (Dependency Injection)
Стоимость улучшения 2: 10 строк кода
Описание: Зависимости (OrderService
,ItemRepository
,OrderRepository
,NotificationService
) теперь внедряются через конструктор, что позволяет легко заменять их на другие реализации и упрощает тестирование. Это делает код более гибким и облегчает внесение изменений в будущем.Улучшение 3: Обработка ошибок
Стоимость улучшения 3: 15 строк кода
Описание: Добавлена обработка исключений и логирование ошибок. В случае недостаточного количества товара на складе выбрасывается исключениеInsufficientStockException
, которое обрабатывается и возвращается соответствующий ответ клиенту. Это делает код более устойчивым к ошибкам и облегчает отладку.Улучшение 4: Использование распределенных транзакций
Стоимость улучшения 4: 10 строк кода
Описание: Создание заказа теперь происходит в рамках распределенной транзакции с использованием пакетаnetsells/laravel-distributed-transactions
. Это гарантирует согласованность данных между базами данных товаров и заказов. Если в процессе создания заказа возникнет ошибка, все изменения будут отменены, что предотвратит несогласованность данных.Улучшение 5: Валидация запроса
Стоимость улучшения 5: 5 строк кода
Описание: Добавлен классCreateOrderRequest
для валидации входных данных запроса на создание заказа. Это позволяет проверять корректность данных перед их обработкой и возвращать информативные сообщения об ошибках в случае некорректных данных.Улучшение 6: Логирование
Стоимость улучшения 6: 7 строк кода
Описание: Добавлено логирование ошибок и предупреждений с помощью фасадаLog
. Это позволяет отслеживать возникающие проблемы и упрощает отладку и мониторинг приложения.Итого: Общая стоимость улучшений составляет 67 строк кода. Несмотря на увеличение количества строк кода, эти улучшения делают код более модульным, поддерживаемым, расширяемым и устойчивым к ошибкам. В долгосрочной перспективе это окупится за счет упрощения поддержки и расширения функциональности приложения.
FanatPHP
12.07.2024 08:56Ну тут уже совсем глюки пошли. Здесь try-catch уже совсем не пришей кобыле хвост, откуда-то выдумал DistributedTransaction, какой-то бессмысленный логгинг.
Предыдущий вариант был сильно лучше.
aleksejs1
12.07.2024 08:56+4Это первая статья подобного рода, которую я решил глянуть...
И... даже не знаю стоит ли это комментировать...
Во-первых, ответы выглядят, как GPT.
Во-вторых, нет связанности. Вторым вопросом идёт Laravel, а 7-ым примеры на symfony.
В-третьих, странные вопросы, не о PHP, а о конкретном стэке: kafka, redis. Т.е. не вопросы типа какие бывают мэсэдж брокеры и кэши, и как они работают, а разбор конкретных технологий.
В-четвёртых, в микросервисах вопросы про PHP, а ответы, не про PHP. "В чём плюсы микросервиса на PHP?" - "В технологической независимовти". Т.е. микросервис на PHP можно написать на GO что ли? :D :D
В-пятых, какой-то странный сет из видов тестирования. Например, есть Security Testing, но нет penetration testing.
Создание библиотеки на PHP - это вопрос? Описание вообще смешное.
Паттерны даже читать не стал.
PeopleMax
12.07.2024 08:56Ну не все такие умные, как вы батенька
aleksejs1
12.07.2024 08:56+2я просто не понимаю в чём ценность этой статьи. Автор не делится опытом. Сет вопросов ничем не обоснован. А ответы такие, что если бы я их получил от кандидата на собеседовании, то уж точно не определил бы его как сеньёра. А вообще, я бы даже и не стал бы такие вопросы задавать... они не показательны
FanatPHP
12.07.2024 08:56Мне кажется, вы одновременно и правы, и неправы. Да, подборка бессистемная и не всегда логичная. Но некоторые вопросы вполне ничего. Я так понимаю, что никто не требует от соискателя зазубрить по пунктам жизненный цикл Ларавля. Но понимание общих принципов, умение представить жизненный цикл в голове будет довольно хорошим показателем, мне кажется. На собеседовании же важно видеть обладает ли человек базовыми знаниями и умеет ли мыслить, делать какие-то построения на их основе. И вопрос про этот пресловутый жизненный цикл в этом смысле вполне ничего?
aleksejs1
12.07.2024 08:56+2А вы читали ответ о циклах в этой статье? Ни слова про ООП, ядро, мидлвэр, шаблонизацию, orm. Зато целый абзац про композер. Серьер бы точно так не отвечал.
https://laravel-docs.com/ru/docs/10.x/lifecycle - есть схожесть с нормальным обьяснением, но на уровне ChatGPT.
И вопрос в целом тоже не хороший. Лучше спрашивать жизненный цикл MVC фреймворка, а не конкретно Laravel.
Laravel оправдан только, если нужен именно человек на Laravel.
MediaRise Автор
12.07.2024 08:56ооп, ядро, мидлвэр, мда, собеседование джунов можете почитать в других статьях.
FanatPHP
12.07.2024 08:56Эээ... Давайте я вам повторю то, что писал в личку
3.Запуск HTTP-запроса:
Приходит HTTP-запрос, и приложение начинает обработку этого запроса
Это какой-то бред. С какой стати фреймворк запускает какие-то запросы? И почему запрос приходит только сейчас? Это синхронный фреймворк, то есть все шаги, начиная с первого, начинают выполняться ПОСЛЕ того как пришел запрос. Я бы предположил, что тут имеется в виду запуск обработки запроса, но это вроде и так есть в 6 пункте.
Давайте вы сначала попробуете объяснить, что здесь имеется в виду, а потом уже поговорим про собеседования джунов?
koreychenko
12.07.2024 08:56+3На личном опыте нащупал странную закономерность, что у кандидатов с опытом Laravel обычно все хуже с паттернами и пониманием как оно все должно работать по сравнению с симфонистами.
Также, если говорить про фуллстэков, то обычно ларавельщики выбирают React, а симфонисты Vue.
MediaRise Автор
12.07.2024 08:56это сугубо ваше мнение, прошедшее через ваш опыт
koreychenko
12.07.2024 08:56+2Безусловно, я так и написал. Возможно мне просто такие разрабы на собеседованиях попадались.
FanatPHP
12.07.2024 08:56+1Мне кажется, это совершенно очевидный факт. Они буквально смотрят в противоположные стороны: симфони специально форсит разделение ответственности и прописывание зависимостей, а Ларавель делает ровно наоборот - все скрывает за удобненькими фасадиками и хелперами.
koreychenko
12.07.2024 08:56+1Вот да, кстати. В симфони, несмотря на всякую магию вроде автовайринга и аргумент резолвинга, бизнес логика старается жить отдельно.
Даже если не хочешь сильно на доктрину завязываться, то можно юзать XML маппинг и в коде сущностей вообще не будет упоминания ORM. В Ларе же этот богомерзкий ActiveRecord. Оно для старта просто и удобно, но связность кратно выше.
FanatPHP
Эта стена текста вызывает двоякое впечатление. Такое ощущение, что сначала статья писалась добротно, потом автор выгорел и начал делать "на отвали", потом присобачил какой-то копипасты, всё это перемешал в случайном порядке и отправил на Хабр. Или это просто запись ответов разных кандидатов, и, как в том анекдоте, "Иногда побеждает тореадор, а иногда - бык"? :D
Раздел про микросервисы, например, написан очень добротно, а жизненный цикл Ларавля наоборот какой-то хлипкий. А у "создания библиотеки" даже и формального вопроса нет, сразу тупо инструкция. Явные глюки перевода, "Основные характеристики Core Domain: максимальная инвестиция", "Какие HTTP-методы API являются идемпотентные".
В целом добротных разделов больше, но очень не хватает оглавления в начале.
MediaRise Автор
Разные темы, где то очень кратко как навигатор для темы для углубленного изучения. Темы из реальных интервью других компаний. Оглавление добавлю
dimas846
Главу про библиотеку 100% искусственный интеллект писал. Другие главы, видимо тоже.