Ещё одна статья: собеседование Senior PHP

от автора

Изображение создано TG ботом Kandinsky

Изображение создано TG ботом Kandinsky

Доброго времени суток, я Едифанов Виталий, являюсь CEO своей компании MediaRise, решил собрать распространённые вопросы для интервью PHP Senior разработчика.

Оглавление:

  1. Что такое Идемпотентность. Ключ идемпотентности. Какие HTTP-методы API являются идемпотентные?

  2. Жизненный цикл laravel

  3. Kafka

  4. Redis

  5. Микросервисы. Плюсы и минусы

  6. Core Domain in DDD

  7. Что такое сервис в DDD

  8. DDD: Entity, Value Object. Различие между ними

  9. DDD: Repository. Collection. Куда поместить логику приложения

  10. Event Sourcing

  11. Виды тестирования

  12. Создание библиотеки на PHP

  13. Как добиться отказоустойчивости между Service A и Service B

  14. Проведи рефакторинг данного кода на PHP. Code Review

  15. SELECT FOR UPDATE

Что такое Идемпотентность. Ключ идемпотентности. Какие HTTP-методы API являются идемпотентные?

  1. Что такое Идемпотентность. Ключ идемпотентности. Какие HTTP-методы API являются идемпотентные?

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

Примеры идемпотентных операций:

1. HTTP-методы: GET, PUT, DELETE — все они идемпотентны, поскольку повторный запрос не изменяет состояние сервера.

2. Обнуление значения переменной в программе: присваивание `x = 0` всегда установит значение переменной x в 0, независимо от того, сколько раз эта операция выполняется.

Ключ идемпотентности — это механизм, используемый для обеспечения идемпотентности в распределенных системах и API. Ключ идемпотентности представляет собой уникальный идентификатор, который клиент предоставляет при выполнении запроса. Сервер использует этот ключ для отслеживания уже выполненных операций и предотвращения их повторного выполнения.

Пример использования ключа идемпотентности:

— Клиент отправляет запрос на создание ресурса с уникальным ключом идемпотентности.

— Сервер сохраняет этот ключ вместе с результатом выполнения запроса.

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

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

Жизненный цикл laravel

  1. Жизненный цикл 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

  1. 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

  1. 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 — это мощный инструмент для работы с данными в реальном времени, который предлагает богатый набор функций и высокую производительность.

Микросервисы. Плюсы и минусы

  1. Микросервисы. Плюсы и минусы

Плюсы микросервисов на 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

  1. 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

  1. Что такое сервис в 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. Различие между ними

  1. 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. Куда поместить логику приложения

  1. 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

  1. 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 включают:

— Возможность воспроизведения всех изменений состояния системы для аудита или восстановления.

— Простота реализации функций, связанных с временными аспектами (например, просмотр состояния системы на конкретный момент времени).

— Улучшенная масштабируемость за счет разделения операций на чтение и запись.

Недостатки могут включать сложность реализации и необходимость управления большим объемом событий.

Виды тестирования

  1. Виды тестирования

В 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

  1. Создание библиотеки на 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

  1. Как добиться отказоустойчивости между 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

  1. Проведи рефакторинг данного кода на 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

  1. 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/