Доброго времени суток, я Едифанов Виталий, являюсь 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:
1. Topics: Темы являются логическими каналами, через которые проходят данные. Данные записываются в темы и читаются из них.
2. Producers: Процедуры отправляют данные в темы.
3. Consumers: Консумеры читают данные из тем.
4. Brokers: Брокеры хранят данные и управляют их распределением между продюсерами и консумерами.
5. Partitions: Темы разделены на разделы (partitions) для обеспечения параллельной обработки данных.
6. 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
1. In-Memory Хранение: Redis хранит все данные в оперативной памяти, что обеспечивает очень высокую скорость чтения и записи. Данные могут быть периодически сохраняться на диск для обеспечения надежности (с использованием механизмов snapshotting и журналирования команд).
2. Однопоточный Дизайн: Redis работает в однопоточном режиме, что упрощает проектирование и устранение состояния гонки, но требует от разработчика тщательной оптимизации команд и использования.
3. Клиент-Серверная Модель: Redis использует модель клиент-сервер, где клиенты взаимодействуют с сервером Redis через сетевые соединения с использованием текстового протокола Redis.
Типы данных
Redis поддерживает различные структуры данных, что делает его очень гибким:
1. Строки (Strings): Это базовый и самый простой тип данных в Redis. Строки могут содержать до 512 МБ данных. Их можно использовать для хранения текстов, чисел и бинарных данных.
2. Списки (Lists): Упорядоченные коллекции строк, которые позволяют добавлять элементы в начало или конец списка. Они полезны для реализации очередей и стеков.
3. Множества (Sets): Неупорядоченные коллекции уникальных строк. Поддерживают операции над множествами, такие как объединение, пересечение и разность.
4. Упорядоченные множества (Sorted Sets): Множества, где каждый элемент имеет связанный с ним «вес» (score). Элементы упорядочены по этому весу. Это полезно для создания рейтинговых таблиц и временных шкал.
5. Хэши (Hashes): Наборы пар ключ-значение, часто используемые для представления объектов и их свойств. Очень полезны для хранения сущностей с множеством атрибутов.
6. Битмапы (Bitmaps): Манипулирование отдельными битами в строках, что позволяет эффективно использовать память для хранения больших наборов данных.
7. Гиперлоглоги (HyperLogLogs): Структура данных для приблизительного подсчета уникальных элементов в наборе, использующая очень мало памяти.
8. Потоки (Streams): Новый тип данных, введенный в Redis 5.0, который позволяет работать с последовательностями записей. Полезно для обработки событий и сообщений.
Сохранение и Устойчивость данных
Redis предлагает два основных механизма для сохранения данных на диск:
1. Snapshotting (RDB): Redis периодически создает моментальные снимки (снапшоты) данных и сохраняет их на диск. Это метод сохранения состояния базы данных на определенные моменты времени.
2. Журналирование команд (AOF): Redis сохраняет каждую операцию записи в журнал (Append Only File). Это позволяет восстановить данные до последнего выполненного изменения.
Репликация и Кластеризация
1. Репликация: Redis поддерживает асинхронную репликацию, где один сервер (мастер) может передавать данные нескольким серверам (репликам). Это улучшает отказоустойчивость и позволяет масштабировать чтение.
2. Кластеры: 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 (общие домены):
1. Core Domain: Это ключевая область, на которую организация должна сосредоточить большинство своих усилий, чтобы добиться успеха. Core Domain требует наибольшего внимания и ресурсов для разработки и поддержки, так как именно она определяет уникальные конкурентные преимущества бизнеса. Например, в интернет-магазине Core Domain может включать в себя механизм персонализированных рекомендаций товаров.
2. Supporting Domain: Эти домены не являются основными источниками конкурентного преимущества, но они поддерживают основной бизнес. Они необходимы для обеспечения бесперебойной работы Core Domain. Например, в том же интернет-магазине поддерживающий домен может включать систему управления складом.
3. 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)
Репозиторий является паттерном для доступа к данным, который обеспечивает абстракцию над источниками данных (например, база данных). Репозиторий позволяет извлекать и сохранять доменные объекты, скрывая детали реализации хранилища данных.
Основные задачи репозитория:
1. Извлечение доменных объектов: предоставление методов для получения объектов на основе различных критериев.
2. Сохранение доменных объектов: предоставление методов для сохранения изменений в доменных объектах.
3. Удаление доменных объектов: предоставление методов для удаления объектов.
Пример интерфейса репозитория:
<?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 коллекции часто используются для управления связями между агрегатами.
Основные задачи коллекции:
1. Управление набором объектов: добавление, удаление, итерация по объектам.
2. Предоставление методов для работы с группой объектов: методы для фильтрации, сортировки и других операций над группой объектов.
Пример коллекции:
<?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 должна быть сосредоточена в доменной модели, то есть в сущностях, значимых объектах и агрегатах. Она не должна находиться в репозиториях или сервисах данных. Основные места для бизнес-логики:
1. Сущности (Entities): объекты с уникальной идентичностью, которые изменяются с течением времени.
2. Объекты значений (Value Objects): объекты без уникальной идентичности, которые неизменяемы.
3. Агрегаты (Aggregates): группы связанных объектов, которые рассматриваются как единое целое для целей изменения данных.
4. Доменные сервисы (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:
1. События: Все изменения состояния системы записываются как события. Событие представляет собой неизменяемую запись, которая описывает изменение состояния.
2. Хранение событий: События хранятся в специальном хранилище событий (Event Store). Это может быть база данных, специально предназначенная для хранения событий, или обычная база данных, адаптированная для этих целей.
3. Восстановление состояния: Для получения текущего состояния системы события читаются и применяются последовательно. Это называется «реплеем» (replay) событий.
4. 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.
1. Зарегистрируйтесь на [Packagist](https://packagist.org/).
2. Создайте репозиторий на GitHub и загрузите туда ваш код.
3. Добавьте ваш репозиторий на 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).
1. Клиент создает заказ через Order Service.
2. Order Service записывает данные о новом заказе в свою базу данных и в той же транзакции добавляет запись в таблицу исходящих сообщений о необходимости уведомить пользователя о создании заказа.
3. Процессор исходящих сообщений Order Service периодически проверяет таблицу исходящих сообщений, видит новое сообщение и отправляет его в Notification Service.
4. Notification Service получает сообщение и отправляет уведомление пользователю.
5. После успешной отправки уведомления, процессор исходящих сообщений Order Service помечает сообщение как отправленное или удаляет его из таблицы.
Преимущества:
— Гарантированная доставка: Паттерн обеспечивает надежную доставку сообщений даже в случае сбоев системы.
— Согласованность данных: Транзакционная запись в таблицу исходящих сообщений обеспечивает консистентность данных.
— Простота восстановления после сбоя: В случае сбоя система может просто перечитать сообщения из таблицы исходящих сообщений и повторить попытку отправки.
Недостатки:
— Сложность реализации: Необходимо реализовать дополнительный компонент (процессор исходящих сообщений) и обеспечить его надежную работу.
— Увеличение задержек: Между записью сообщения и его фактической отправкой может быть задержка, зависящая от частоты опроса таблицы исходящих сообщений.
Заключение:
Pattern Outbox является мощным инструментом для обеспечения надежной передачи данных в распределенных системах. Он широко используется в микросервисной архитектуре для обеспечения согласованности и надежности обмена сообщениями между сервисами.
Проведи рефакторинг данного кода на PHP. Code Review
-
Проведи рефакторинг данного кода на PHP. Code Review
Рефакторинг кода поможет улучшить его читаемость, поддерживаемость и масштабируемость.
Вот подробное объяснение изменений и сам рефакторинг кода:
Основные изменения:
1. Внедрение зависимостей (Dependency Injection): Репозитории и сервисы теперь передаются через конструктор. Это делает код более гибким и тестируемым.
2. Интерфейсы: Использование интерфейсов для уменьшения связности кода и упрощения замены реализаций.
3. Принцип единственной ответственности: Каждый класс выполняет только одну функцию (а именно у нас одна причина на изменение кода), что соответствует принципу единственной ответственности (SRP).
4. Улучшение читаемости: Код стал более организованным и поддерживаемым благодаря разделению ответственности.
Рефакторинг:
<?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)
ссылка на оригинал статьи https://habr.com/ru/articles/828324/
Добавить комментарий