Ищете работу backend разработчика и хотите быть на высоте на собеседовании?

Подготовка к вопросам для backend разработчика на PHP и Go может оказаться непростой задачей. В этой статье мы разберем некоторые вопросы для Senior Backend разработчика, которые помогут вам пройти собеседование и произвести впечатление на потенциального работодателя.

Почему Важно Готовиться к Вопросам для Backend Разработчика?

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

Вопрос: Конкурентность и параллелизм что лучше и зачем нужно

Хорошая статья на эту ? тему, а ниже моё объяснение:

Конкурентность и параллелизм — два различных, но связанных подхода для решения множества задач. Разберем, что они означают, какие у них преимущества и когда они необходимы. Пример на хомяках и не только.

Конкурентность (Concurrency)
Конкурентность (Concurrency)
Параллелизм (Parallelism). P.S. Также параллелизм может содержать в себе конкурентность, например хомяки одинакого цвета, будут стоять в очереди за желудем предназначенный именно для хомяков с определенным цветом
Параллелизм (Parallelism). P.S. Также параллелизм может содержать в себе конкурентность, например хомяки одинакого цвета, будут стоять в очереди за желудем предназначенный именно для хомяков с определенным цветом

Конкурентность (Concurrency)

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

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

Преимущества конкурентности:

  1. Повышение производительности: Конкурентные программы могут выполнять задачи быстрее за счет лучшего использования ресурсов.

  2. Отзывчивость: Интерфейс пользователя может оставаться отзывчивым, даже если происходят длительные вычисления.

  3. Эффективное использование ресурсов: Время простоя процессора сокращается, так как задачи могут выполняться, пока другие ждут завершения операций ввода-вывода.

Недостатки:

  1. Сложность разработки: Конкурентные программы сложнее разрабатывать и отлаживать из-за возможных состояний гонки и взаимоблокировок.

  2. Непредсказуемость: Результаты выполнения могут быть различными при каждом запуске.

Параллелизм (Parallelism)

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

Преимущества параллелизма:

  1. Увеличение скорости выполнения: Параллельное выполнение позволяет значительно ускорить выполнение задач, разделяемых на независимые части.

  2. Масштабируемость: Параллельные программы могут использовать большее количество процессоров для выполнения задач.

Недостатки:

  1. Требования к аппаратуре: Для реализации параллелизма необходимы многопроцессорные или многоядерные системы.

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

Когда и зачем нужны конкурентность и параллелизм?

  1. Конкурентность:

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

    • Полезна в приложениях, где необходимо поддерживать отзывчивость интерфейса пользователя.

  2. Параллелизм:

    • Эффективен для вычислительно интенсивных задач, таких как обработка больших данных, научные вычисления и рендеринг графики.

    • Подходит для задач, которые могут быть легко разделены на независимые части, выполняемые параллельно.

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

Конкурентность и параллелизм в GO lang

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

Конкурентность в Go

Go был разработан с учетом необходимости простой реализации конкурентных программ. Основные средства для этого:

  1. Горутины (goroutines):

    • Легковесные потоки, управляемые планировщиком Go, которые позволяют выполнять функции конкурентно.

    • Создаются с помощью ключевого слова go. Пример:

      go func() {
          // код, выполняемый конкурентно
      }()
      
  2. Каналы (channels):

    • Синхронизированные очереди для передачи данных между горутинами.

    • Создаются с помощью функции make. Пример:

      ch := make(chan int)

Преимущества конкурентности в Go:

  • Простота использования: Go предлагает простые и интуитивные механизмы для создания конкурентных программ.

  • Легковесность: Горутины значительно легче потоков операционной системы, что позволяет запускать тысячи и миллионы горутин в одной программе.

  • Безопасность: Каналы обеспечивают безопасную передачу данных между горутинами, избегая сложностей с общими данными и блокировками.

Параллелизм в Go

Параллелизм в Go достигается за счет использования нескольких процессоров или ядер для одновременного выполнения горутин. Go использует собственный планировщик для управления параллельным выполнением.

Настройка параллелизма:

  • Количество используемых процессоров можно задать с помощью функции runtime.GOMAXPROCS. Пример:

    runtime.GOMAXPROCS(4) // Использовать 4 процессора

Преимущества параллелизма в Go:

  • Повышение производительности: Параллельное выполнение задач позволяет значительно ускорить их выполнение на многопроцессорных системах.

  • Эффективное использование ресурсов: Планировщик Go эффективно распределяет горутины по доступным процессорам.

Что лучше использовать в Go и зачем?

  1. Конкурентность:

    • Подходит для задач, где важна отзывчивость и управление многозадачностью, например, для сетевых серверов, где нужно обрабатывать множество запросов одновременно.

    • Полезна для асинхронных операций, таких как выполнение длительных операций ввода-вывода, без блокировки основного потока выполнения.

  2. Параллелизм:

    • Эффективен для задач, требующих интенсивных вычислений, которые можно разделить на независимые части.

    • Подходит для обработки больших объемов данных, рендеринга графики и выполнения сложных математических вычислений.

Примеры использования

Конкурентность:

package main

import (
    "fmt"
    "time"
)

func say(s string) {
    for i := 0; i < 5; i++ {
        fmt.Println(s)
        time.Sleep(100 * time.Millisecond)
    }
}

func main() {
    go say("world")
    say("hello")
}

Параллелизм:

package main

import (
    "fmt"
    "runtime"
)

func fib(n int, c chan int) {
    x, y := 0, 1
    for i := 0; i < n; i++ {
        x, y = y, x+y
    }
    c <- x
}

func main() {
    runtime.GOMAXPROCS(4) // Используем 4 процессора

    c := make(chan int)
    for i := 0; i < 10; i++ {
        go fib(i, c)
    }

    for i := 0; i < 10; i++ {
        fmt.Println(<-c)
    }
}

Итог

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

Вопрос: Наследование или композиция? Что решает? Что предпочитаете?

Отличная статья на эту ? тему

  • Наследование и композиция — это два основных способа организации кода в объектно-ориентированном программировании (ООП). Оба подхода имеют свои плюсы и минусы и применяются в зависимости от контекста и требований к проекту.

    Наследование

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

    Когда использовать наследование:

    1. Когда у вас есть четкая иерархия "is-a".

    2. Когда подкласс должен использовать большую часть функциональности суперкласса.

    3. Когда вы хотите полиморфизм (возможность использовать объекты подклассов как объекты суперкласса).

    Пример на PHP:

    <?php
    
    class Animal {
        public function makeSound() {
            echo "Some generic animal sound\n";
        }
    }
    
    class Dog extends Animal {
        public function makeSound() {
            echo "Woof! Woof!\n";
        }
    }
    
    class Cat extends Animal {
        public function makeSound() {
            echo "Meow! Meow!\n";
        }
    }
    
    $dog = new Dog();
    $dog->makeSound(); // Woof! Woof!
    
    $cat = new Cat();
    $cat->makeSound(); // Meow! Meow!
    ?>
    

    Композиция

    Композиция используется, когда объекты состоят из других объектов, и при этом не требуется жесткая иерархия "is-a". Композиция позволяет гибко комбинировать разные объекты, чтобы создавать сложные функциональности.

    Когда использовать композицию:

    1. Когда объекты могут быть составлены из множества различных компонентов.

    2. Когда функциональность должна быть добавлена к классу без изменения его основной природы.

    3. Когда вам нужно изменить поведение во время выполнения путем подмены компонентов.

    Пример на PHP:

    <?php
    
    class Engine {
        public function start() {
            echo "Engine started\n";
        }
    }
    
    class Transmission {
        public function engage() {
            echo "Transmission engaged\n";
        }
    }
    
    class Car {
        private $engine;
        private $transmission;
    
        public function __construct(Engine $engine, Transmission $transmission) {
            $this->engine = $engine;
            $this->transmission = $transmission;
        }
    
        public function drive() {
            $this->engine->start();
            $this->transmission->engage();
            echo "Car is driving\n";
        }
    }
    
    $engine = new Engine();
    $transmission = new Transmission();
    $car = new Car($engine, $transmission);
    $car->drive(); // Engine started, Transmission engaged, Car is driving
    ?>
    

    Выводы

    • Используйте наследование, когда у вас есть четкая иерархия и когда объекты можно логически рассматривать как типы суперкласса.

    • Оба класса из одной предметной области

    • Код предка необходим либо хорошо подходит для наследника

    • Наследник в основном добавляет логику классу

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

    • Если вам необходимо изменить логику класса

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

Вопрос: Чистая архитектура

Подробнее прочитать в этой ? статье

  • Чистая архитектура (Clean Architecture) — это набор принципов и шаблонов проектирования программного обеспечения, предложенный Робертом С. Мартином (Robert C. Martin), также известным как Uncle Bob. Основная цель Чистой архитектуры — создание гибкой, легко поддерживаемой и тестируемой системы. Основные принципы этой архитектуры включают:

    1. Независимость от фреймворков: Архитектура не должна зависеть от конкретных библиотек или фреймворков.

    2. Тестируемость: Код должен быть легко тестируемым.

    3. Независимость от UI: Интерфейс пользователя может изменяться без необходимости изменения бизнес-логики или базы данных.

    4. Независимость от базы данных: Система не должна зависеть от конкретной технологии хранения данных.

    5. Независимость от внешних агентств: Внешние сервисы и инструменты могут изменяться без изменения бизнес-логики.

    Основные слои Чистой архитектуры

    1. Entities (Сущности): Содержат бизнес-логику и правила. Они независимы от любых других частей системы.

    2. Use Cases (Сценарии использования): Координируют поток данных к и от сущностей. Они определяют действия, которые могут быть выполнены над сущностями.

    3. Interface Adapters (Адаптеры интерфейсов): Содержат детали преобразования данных, чтобы они могли быть переданы из и в сценарии использования и сущности.

    4. Frameworks and Drivers (Фреймворки и драйверы): Содержат внешние средства и библиотеки, такие как базы данных, UI, веб-фреймворки и т.д.

    Пример на PHP

    Рассмотрим пример приложения для управления задачами.

    1. Entities (Сущности)

    <?
    // Task.php
    class Task {
        private $id;
        private $title;
        private $description;
        private $status;
    
        public function __construct($id, $title, $description, $status) {
            $this->id = $id;
            $this->title = $title;
            $this->description = $description;
            $this->status = $status;
        }
    
        // Геттеры и сеттеры
        public function getId() {
            return $this->id;
        }
    
        public function getTitle() {
            return $this->title;
        }
    
        public function getDescription() {
            return $this->description;
        }
    
        public function getStatus() {
            return $this->status;
        }
    
        public function setStatus($status) {
            $this->status = $status;
        }
    }
    

    2. Use Cases (Сценарии использования)

    <?
    // TaskRepositoryInterface.php
    interface TaskRepositoryInterface {
        public function findById($id);
        public function save(Task $task);
    }
    
    // CreateTaskUseCase.php
    class CreateTaskUseCase {
        private $taskRepository;
    
        public function __construct(TaskRepositoryInterface $taskRepository) {
            $this->taskRepository = $taskRepository;
        }
    
        public function execute($title, $description) {
            $task = new Task(null, $title, $description, 'pending');
            $this->taskRepository->save($task);
        }
    }
    
    // CompleteTaskUseCase.php
    class CompleteTaskUseCase {
        private $taskRepository;
    
        public function __construct(TaskRepositoryInterface $taskRepository) {
            $this->taskRepository = $taskRepository;
        }
    
        public function execute($id) {
            $task = $this->taskRepository->findById($id);
            $task->setStatus('completed');
            $this->taskRepository->save($task);
        }
    }
    

    3. Interface Adapters (Адаптеры интерфейсов)

    <?
    // InMemoryTaskRepository.php
    class InMemoryTaskRepository implements TaskRepositoryInterface {
        private $tasks = [];
    
        public function findById($id) {
            foreach ($this->tasks as $task) {
                if ($task->getId() == $id) {
                    return $task;
                }
            }
            return null;
        }
    
        public function save(Task $task) {
            if ($task->getId() === null) {
                $taskId = count($this->tasks) + 1;
                $reflection = new ReflectionClass($task);
                $idProperty = $reflection->getProperty('id');
                $idProperty->setAccessible(true);
                $idProperty->setValue($task, $taskId);
            }
            $this->tasks[$task->getId()] = $task;
        }
    }
    

    4. Frameworks and Drivers (Фреймворки и драйверы)

    <?
    // index.php
    require 'Task.php';
    require 'TaskRepositoryInterface.php';
    require 'CreateTaskUseCase.php';
    require 'CompleteTaskUseCase.php';
    require 'InMemoryTaskRepository.php';
    
    $taskRepository = new InMemoryTaskRepository();
    $createTaskUseCase = new CreateTaskUseCase($taskRepository);
    $completeTaskUseCase = new CompleteTaskUseCase($taskRepository);
    
    // Создаем задачу
    $createTaskUseCase->execute('Изучить Чистую архитектуру', 'Изучить принципы и примеры Чистой архитектуры');
    $task = $taskRepository->findById(1);
    echo 'Задача создана: ' . $task->getTitle() . PHP_EOL;
    
    // Завершаем задачу
    $completeTaskUseCase->execute(1);
    $task = $taskRepository->findById(1);
    echo 'Статус задачи: ' . $task->getStatus() . PHP_EOL;
    

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

Вопрос: SOLID расшифровать. Тут важно как вы его понимаете, для чего нужно и рассказать на примерах.

  • SOLID — это акроним, который обозначает пять принципов объектно-ориентированного программирования, направленных на улучшение структуры и качества кода. Эти принципы были сформулированы Робертом Мартином (Robert C. Martin) и включают следующие:

    1. Single Responsibility Principle (SRP) - Принцип единственной ответственности:
      Каждый класс должен иметь одну и только одну причину для изменения, то есть только одну ответственность.

    2. Open/Closed Principle (OCP) - Принцип открытости/закрытости:
      Программные сущности должны быть открыты для расширения, но закрыты для модификации.

    3. Liskov Substitution Principle (LSP) - Принцип подстановки Барбары Лисков:
      Объекты в программе должны заменяться экземплярами их подтипов без изменения правильности выполнения программы.

    4. Interface Segregation Principle (ISP) - Принцип разделения интерфейса:
      Клиенты не должны зависеть от интерфейсов, которые они не используют. Лучше много специализированных интерфейсов, чем один общий.

    5. Dependency Inversion Principle (DIP) - Принцип инверсии зависимостей:
      Зависимости должны строиться относительно абстракций, а не конкретных классов.

    Давайте рассмотрим пример на PHP, где применяются эти принципы.

    Пример применения SOLID на PHP

    Single Responsibility Principle (SRP)

    Каждый класс имеет единственную ответственность.

    <?
    class Report {
        public function generate() {
            // Генерация отчета
        }
    }
    
    class ReportPrinter {
        public function print(Report $report) {
            // Печать отчета
        }
    }
    

    Open/Closed Principle (OCP)

    Классы должны быть открыты для расширения, но закрыты для модификации.

    <?
    abstract class Shape {
        abstract public function area();
    }
    
    class Circle extends Shape {
        private $radius;
    
        public function __construct($radius) {
            $this->radius = $radius;
        }
    
        public function area() {
            return pi() * $this->radius * $this->radius;
        }
    }
    
    class Rectangle extends Shape {
        private $width;
        private $height;
    
        public function __construct($width, $height) {
            $this->width = $width;
            $this->height = $height;
        }
    
        public function area() {
            return $this->width * $this->height;
        }
    }
    

    Liskov Substitution Principle (LSP)

    Подтипы должны заменять базовые типы.

    <?
    class Bird {
        public function fly() {
            // Птица летает
        }
    }
    
    class Sparrow extends Bird {
        public function fly() {
            // Воробей летает
        }
    }
    
    class Ostrich extends Bird {
        public function fly() {
            throw new Exception("Страус не умеет летать");
        }
    }
    
    // Лучше сделать базовый класс FlyableBird и наследовать его тем, кто может летать
    class FlyableBird extends Bird {
        public function fly() {
            // Летает
        }
    }
    
    class Sparrow extends FlyableBird {
        public function fly() {
            // Воробей летает
        }
    }
    
    class Ostrich extends Bird {
        // Страус не летает, нет метода fly
    }
    

    Interface Segregation Principle (ISP)

    Интерфейсы должны быть специализированы.

    <?
    interface Worker {
        public function work();
    }
    
    interface Eater {
        public function eat();
    }
    
    class Human implements Worker, Eater {
        public function work() {
            // Человек работает
        }
    
        public function eat() {
            // Человек ест
        }
    }
    
    class Robot implements Worker {
        public function work() {
            // Робот работает
        }
    }
    

    Dependency Inversion Principle (DIP)

    Зависимости должны строиться на абстракциях.

    <?
    interface DatabaseConnection {
        public function connect();
    }
    
    class MySQLConnection implements DatabaseConnection {
        public function connect() {
            // Подключение к MySQL
        }
    }
    
    class PasswordReminder {
        private $dbConnection;
    
        public function __construct(DatabaseConnection $dbConnection) {
            $this->dbConnection = $dbConnection;
        }
    }
    

    В этом примере PasswordReminder зависит не от конкретной реализации MySQLConnection, а от абстракции DatabaseConnection, что позволяет легко менять тип подключения к базе данных.

Вопрос: DDD, к какому слою относится репозиторий?

Domain-Driven Design (DDD) — это подход к разработке программного обеспечения, который фокусируется на комплексных бизнес-доменах и их логике. DDD направлен на создание модели домена, которая будет использоваться во всех частях системы, обеспечивая согласованность и понятность кода. Основные принципы DDD включают:

  • Модель домена: представляет сущности и их взаимодействия.

    1. Управляемые контексты (Bounded Contexts): разграничение областей ответственности, чтобы предотвратить смешивание логики из разных частей системы.

    2. Язык домена (Ubiquitous Language): общий язык, используемый разработчиками и бизнес-экспертами для описания системы.

    3. Агрегаты: группы связанных объектов, которые рассматриваются как единое целое.

    4. События домена: изменения состояния системы, которые могут влиять на другие части системы.

    Слои в DDD

    DDD часто подразумевает разделение приложения на несколько слоев, каждый из которых выполняет свои функции:

    1. User Interface (UI): отвечает за взаимодействие с пользователем.

    2. Application Layer: координирует выполнение задач, делегируя их соответствующим частям системы.

    3. Domain Layer: содержит бизнес-логику и модели домена.

    4. Infrastructure Layer: предоставляет технические детали, такие как взаимодействие с базой данных и внешними системами.

    Репозиторий в DDD

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

    Пример на PHP

    Рассмотрим пример, как можно организовать репозиторий в рамках DDD на PHP.

    Модель домена

    <?
    class User {
        private $id;
        private $name;
        private $email;
    
        public function __construct($id, $name, $email) {
            $this->id = $id;
            $this->name = $name;
            $this->email = $email;
        }
    
        public function getId() {
            return $this->id;
        }
    
        public function getName() {
            return $this->name;
        }
    
        public function getEmail() {
            return $this->email;
        }
    }
    

    Интерфейс репозитория

    <?
    interface UserRepository {
        public function findById($id);
        public function save(User $user);
        public function delete(User $user);
    }
    

    Реализация репозитория

    <?
    class MySQLUserRepository implements UserRepository {
        private $connection;
    
        public function __construct(PDO $connection) {
            $this->connection = $connection;
        }
    
        public function findById($id) {
            $stmt = $this->connection->prepare('SELECT * FROM users WHERE id = :id');
            $stmt->bindParam(':id', $id);
            $stmt->execute();
            $data = $stmt->fetch(PDO::FETCH_ASSOC);
    
            if ($data) {
                return new User($data['id'], $data['name'], $data['email']);
            }
    
            return null;
        }
    
        public function save(User $user) {
            if ($this->findById($user->getId())) {
                $stmt = $this->connection->prepare('UPDATE users SET name = :name, email = :email WHERE id = :id');
            } else {
                $stmt = $this->connection->prepare('INSERT INTO users (id, name, email) VALUES (:id, :name, :email)');
            }
    
            $stmt->bindParam(':id', $user->getId());
            $stmt->bindParam(':name', $user->getName());
            $stmt->bindParam(':email', $user->getEmail());
            $stmt->execute();
        }
    
        public function delete(User $user) {
            $stmt = $this->connection->prepare('DELETE FROM users WHERE id = :id');
            $stmt->bindParam(':id', $user->getId());
            $stmt->execute();
        }
    }
    

    Использование репозитория

    <?
    $pdo = new PDO('mysql:host=localhost;dbname=mydatabase', 'username', 'password');
    $userRepository = new MySQLUserRepository($pdo);
    
    // Создание нового пользователя
    $newUser = new User(1, 'John Doe', 'john.doe@example.com');
    $userRepository->save($newUser);
    
    // Получение пользователя по ID
    $user = $userRepository->findById(1);
    echo $user->getName(); // Выведет "John Doe"
    
    // Удаление пользователя
    $userRepository->delete($user);
    

    Этот пример демонстрирует, как можно организовать репозиторий для управления данными в рамках подхода DDD на PHP. Репозиторий инкапсулирует все детали взаимодействия с базой данных, предоставляя простой интерфейс для доменного слоя.

Вопрос: Расскажите о паттерне Outbox

  • Transactional outbox pattern

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

    Основные концепции

    1. Транзакционная таблица Outbox:

      • Все сообщения, которые должны быть отправлены, сначала записываются в таблицу базы данных, обычно называемую outbox.

      • Запись сообщения в таблицу outbox выполняется в рамках той же транзакции, что и бизнес-операция, для которой генерируется сообщение.

    2. Периодический процессор Outbox:

      • Отдельный процесс или поток периодически проверяет таблицу outbox на наличие новых сообщений.

      • Если он находит сообщения, он обрабатывает их и отправляет в целевую систему или брокер сообщений (например, Kafka).

      • После успешной отправки сообщения помечаются как отправленные или удаляются из таблицы outbox.

    Пример реализации на Go

    Рассмотрим пример применения паттерна Transactional Outbox на языке Go с использованием базы данных PostgreSQL и Kafka в качестве брокера сообщений.

    Структура проекта

    go-transactional-outbox/
    ├── main.go
    ├── outbox_processor.go
    ├── models/
    │   └── outbox.go
    ├── database/
    │   └── database.go
    └── kafka/
        └── kafka.go
    

    Основной файл main.go

    package main
    
    import (
        "context"
        "fmt"
        "log"
        "time"
    
        "github.com/jmoiron/sqlx"
        _ "github.com/lib/pq"
    
        "go-transactional-outbox/database"
        "go-transactional-outbox/models"
        "go-transactional-outbox/outbox_processor"
    )
    
    func main() {
        db, err := database.ConnectDB()
        if err != nil {
            log.Fatal(err)
        }
    
        // Пример бизнес-логики с использованием транзакционной outbox
        ctx := context.Background()
        tx, err := db.BeginTx(ctx, nil)
        if err != nil {
            log.Fatal(err)
        }
    
        // Выполнение бизнес-операции
        _, err = tx.ExecContext(ctx, "INSERT INTO users (name) VALUES ($1)", "John Doe")
        if err != nil {
            tx.Rollback()
            log.Fatal(err)
        }
    
        // Запись события в таблицу outbox в рамках той же транзакции
        outboxEntry := models.OutboxEntry{
            ID:        "1",
            Payload:   `{"event":"UserCreated","name":"John Doe"}`,
            Status:    "pending",
            CreatedAt: time.Now(),
        }
        _, err = tx.NamedExecContext(ctx, `INSERT INTO outbox (id, payload, status, created_at) VALUES (:id, :payload, :status, :created_at)`, &outboxEntry)
        if err != nil {
            tx.Rollback()
            log.Fatal(err)
        }
    
        err = tx.Commit()
        if err != nil {
            log.Fatal(err)
        }
    
        // Запуск процессора outbox
        go outbox_processor.Start(db)
    
        // Запуск основного приложения
        fmt.Println("Application started. Press Ctrl+C to exit.")
        select {}
    }
    

    Модели данных models/outbox.go

    package models
    
    import "time"
    
    type OutboxEntry struct {
        ID        string    `db:"id"`
        Payload   string    `db:"payload"`
        Status    string    `db:"status"`
        CreatedAt time.Time `db:"created_at"`
        UpdatedAt time.Time `db:"updated_at"`
    }
    

    Соединение с базой данных database/database.go

    package database
    
    import (
        "github.com/jmoiron/sqlx"
        _ "github.com/lib/pq"
    )
    
    func ConnectDB() (*sqlx.DB, error) {
        db, err := sqlx.Connect("postgres", "user=youruser dbname=yourdb sslmode=disable password=yourpassword")
        if err != nil {
            return nil, err
        }
        return db, nil
    }
    

    Процессор outbox outbox_processor.go

    package outbox_processor
    
    import (
        "context"
        "log"
        "time"
    
        "github.com/jmoiron/sqlx"
        "go-transactional-outbox/models"
        "go-transactional-outbox/kafka"
    )
    
    func Start(db *sqlx.DB) {
        for {
            processOutboxEntries(db)
            time.Sleep(10 * time.Second) // Периодическое выполнение каждые 10 секунд
        }
    }
    
    func processOutboxEntries(db *sqlx.DB) {
        var entries []models.OutboxEntry
        err := db.Select(&entries, "SELECT * FROM outbox WHERE status = 'pending'")
        if err != nil {
            log.Println("Error fetching outbox entries:", err)
            return
        }
    
        for _, entry := range entries {
            err = kafka.SendMessage(entry.Payload)
            if err != nil {
                log.Println("Error sending message to Kafka:", err)
                continue
            }
    
            _, err = db.Exec("UPDATE outbox SET status = 'processed', updated_at = $1 WHERE id = $2", time.Now(), entry.ID)
            if err != nil {
                log.Println("Error updating outbox entry:", err)
            }
        }
    }
    

    Отправка сообщений в Kafka kafka/kafka.go

    package kafka
    
    import (
        "log"
    
        "github.com/Shopify/sarama"
    )
    
    func SendMessage(message string) error {
        producer, err := sarama.NewSyncProducer([]string{"localhost:9092"}, nil)
        if err != nil {
            log.Println("Error creating Kafka producer:", err)
            return err
        }
        defer producer.Close()
    
        msg := &sarama.ProducerMessage{
            Topic: "your_topic",
            Value: sarama.StringEncoder(message),
        }
    
        _, _, err = producer.SendMessage(msg)
        if err != nil {
            log.Println("Error sending message to Kafka:", err)
            return err
        }
    
        return nil
    }
    

    Заключение

    Этот пример демонстрирует основные шаги по реализации паттерна Transactional Outbox на Go с использованием PostgreSQL и Kafka. Реализация паттерна позволяет гарантировать, что сообщения отправляются только после успешного выполнения основной бизнес-операции, тем самым обеспечивая целостность данных в распределенной системе.

Вопрос: Что такое CQRS

  • Command Query Responsibility Segregation (CQRS) — это архитектурный паттерн, который разделяет операции изменения данных (команды) и операции чтения данных (запросы) в разных моделях. Основная идея в том, чтобы оптимизировать производительность, масштабируемость и безопасность, раздельно обрабатывая команды и запросы.

    Основные принципы CQRS:

    1. Разделение команд и запросов:

      • Команды: изменения состояния системы (создание, обновление, удаление данных).

      • Запросы: получение данных без изменения состояния.

    2. Разделение моделей данных:

      • Модель команд: оптимизирована для записи данных.

      • Модель запросов: оптимизирована для чтения данных.

    3. Асинхронность:

      • В большинстве случаев команды и запросы обрабатываются асинхронно, что позволяет повысить производительность и масштабируемость.

    4. Изолированное управление данными:

      • В зависимости от сложности системы, можно использовать разные базы данных для команд и запросов.

    Пример на PHP

    Для простоты, мы рассмотрим упрощенную реализацию CQRS на PHP.

    1. Команда: Создание нового пользователя

    Command:

    <?
    // CreateUserCommand.php
    class CreateUserCommand {
        private string $name;
        private string $email;
    
        public function __construct(string $name, string $email) {
            $this->name = $name;
            $this->email = $email;
        }
    
        public function getName(): string {
            return $this->name;
        }
    
        public function getEmail(): string {
            return $this->email;
        }
    }
    

    Handler:

    <?
    // CreateUserHandler.php
    class CreateUserHandler {
        private UserRepository $userRepository;
    
        public function __construct(UserRepository $userRepository) {
            $this->userRepository = $userRepository;
        }
    
        public function handle(CreateUserCommand $command): void {
            $user = new User($command->getName(), $command->getEmail());
            $this->userRepository->save($user);
        }
    }
    

    2. Запрос: Получение списка пользователей

    Query:

    <?
    // GetUsersQuery.php
    class GetUsersQuery {
        // Параметры запроса (если нужны)
    }
    

    Handler:

    <?
    // GetUsersHandler.php
    class GetUsersHandler {
        private UserRepository $userRepository;
    
        public function __construct(UserRepository $userRepository) {
            $this->userRepository = $userRepository;
        }
    
        public function handle(GetUsersQuery $query): array {
            return $this->userRepository->findAll();
        }
    }
    

    3. Репозиторий: Работа с данными

    UserRepository:

    <?
    // UserRepository.php
    class UserRepository {
        private PDO $dbConnection;
    
        public function __construct(PDO $dbConnection) {
            $this->dbConnection = $dbConnection;
        }
    
        public function save(User $user): void {
            $stmt = $this->dbConnection->prepare("INSERT INTO users (name, email) VALUES (:name, :email)");
            $stmt->execute(['name' => $user->getName(), 'email' => $user->getEmail()]);
        }
    
        public function findAll(): array {
            $stmt = $this->dbConnection->query("SELECT * FROM users");
            return $stmt->fetchAll(PDO::FETCH_ASSOC);
        }
    }
    

    4. Модель пользователя

    User:

    <?
    // User.php
    class User {
        private string $name;
        private string $email;
    
        public function __construct(string $name, string $email) {
            $this->name = $name;
            $this->email = $email;
        }
    
        public function getName(): string {
            return $this->name;
        }
    
        public function getEmail(): string {
            return $this->email;
        }
    }
    

    5. Пример использования

    Пример:

    <?
    // index.php
    
    // Подключение к базе данных
    $dbConnection = new PDO('mysql:host=localhost;dbname=test', 'root', '');
    
    // Репозиторий
    $userRepository = new UserRepository($dbConnection);
    
    // Обработчик команды
    $createUserHandler = new CreateUserHandler($userRepository);
    $createUserCommand = new CreateUserCommand('John Doe', 'john@example.com');
    $createUserHandler->handle($createUserCommand);
    
    // Обработчик запроса
    $getUsersHandler = new GetUsersHandler($userRepository);
    $getUsersQuery = new GetUsersQuery();
    $users = $getUsersHandler->handle($getUsersQuery);
    
    print_r($users);
    

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

Вопрос: Object-relational mapping (ORM), ActiveRecord, Doctrine

  • Object-Relational Mapping (ORM)

    Что такое ORM?

    Object-Relational Mapping (ORM) — это метод программирования, который используется для преобразования данных между несовместимыми типами систем, таких как объектно-ориентированные системы программирования и реляционные базы данных. ORM позволяет разработчикам работать с базами данных в терминах объектов, а не напрямую с таблицами и запросами SQL.

    Основные преимущества ORM:

    • Абстракция баз данных: ORM предоставляет уровень абстракции между приложением и базой данных, что упрощает работу с данными.

    • Ускорение разработки: Сокращает количество написанного кода, особенно шаблонного SQL.

    • Поддержка нескольких баз данных: ORM обычно поддерживает различные СУБД, что упрощает переносимость кода между разными системами баз данных.

    ActiveRecord

    Что такое ActiveRecord?

    ActiveRecord — это шаблон проектирования, который используется в ORM для работы с базами данных. В этом шаблоне каждая таблица базы данных представлена классом, а строки в таблице — объектами этого класса. ActiveRecord отвечает за управление доступом к данным и выполнение основных операций CRUD (Create, Read, Update, Delete).

    Преимущества ActiveRecord:

    1. Простота использования: Модели ActiveRecord интуитивно понятны и просты в использовании, особенно для разработчиков, которые только начинают работать с ORM (Object-Relational Mapping).

    2. Меньше кода: Меньше кода требуется для основных операций CRUD (создание, чтение, обновление, удаление).

    3. Единая ответственность: Модели отвечают за свои данные, что упрощает понимание кода.

    4. Интеграция с фреймворками: Многие веб-фреймворки (например, Ruby on Rails) используют ActiveRecord, что облегчает разработку приложений.

    Недостатки ActiveRecord:

    1. Нарушение принципа единой ответственности: Модели могут быть перегружены как бизнес-логикой, так и логикой доступа к данным.

    2. Сложность масштабирования: В больших проектах может стать сложным управлять сложными запросами и бизнес-логикой в одной модели.

    3. Слабая поддержка сложных бизнес-правил: Модели ActiveRecord могут затруднить реализацию сложных бизнес-правил, которые не укладываются в стандартные операции CRUD.

    Пример использования ActiveRecord на PHP (с использованием фреймворка Laravel):

    <?
    // Создание новой записи в таблице users
    $user = new User;
    $user->name = 'John Doe';
    $user->email = 'john@example.com';
    $user->save();
    
    // Получение всех записей из таблицы users
    $users = User::all();
    
    // Обновление записи в таблице users
    $user = User::find(1);
    $user->email = 'newemail@example.com';
    $user->save();
    
    // Удаление записи из таблицы users
    $user = User::find(1);
    $user->delete();
    

    Data Mapper

    Data Mapper – это шаблон проектирования, в котором отдельный объект (мэппер) отвечает за перевод данных между объектами в памяти и базой данных. Модели в этом случае не знают о базе данных и не содержат SQL-запросов. Вместо этого мэппер управляет этой задачей, обеспечивая разделение бизнес-логики и логики доступа к данным.

    Преимущества Data Mapper:

    1. Четкое разделение ответственности: Модели и мэпперы имеют четко разделенные обязанности, что облегчает поддержку и масштабирование кода.

    2. Гибкость и тестируемость: Код становится более гибким и легче тестируется, так как модели не зависят от базы данных.

    3. Поддержка сложных бизнес-правил: Легче реализовывать и поддерживать сложные бизнес-правила и логику.

    4. Поддержка нескольких баз данных: Упрощает работу с несколькими базами данных или нестандартными хранилищами данных.

    Недостатки Data Mapper:

    1. Увеличение сложности: Разработка на основе Data Mapper требует большего количества кода и более сложной структуры по сравнению с ActiveRecord.

    2. Больше времени на разработку: Больше времени требуется на разработку и настройку, так как нужно писать и поддерживать мэпперы.

    3. Порог входа: Разработчики, не знакомые с шаблоном, могут найти его сложным для освоения.

    Пример использования Data Mapper (Doctrine) на PHP:

    <?
    // Определение сущности (Entity)
    use Doctrine\ORM\Mapping as ORM;
    
    /**
     * @ORM\Entity
     * @ORM\Table(name="users")
     */
    class User
    {
        /** 
         * @ORM\Id
         * @ORM\Column(type="integer") 
         * @ORM\GeneratedValue 
         */
        protected $id;
    
        /** @ORM\Column(type="string") */
        protected $name;
    
        /** @ORM\Column(type="string") */
        protected $email;
    
        // Геттеры и сеттеры
        public function getId()
        {
            return $this->id;
        }
    
        public function getName()
        {
            return $this->name;
        }
    
        public function setName($name)
        {
            $this->name = $name;
        }
    
        public function getEmail()
        {
            return $this->email;
        }
    
        public function setEmail($email)
        {
            $this->email = $email;
        }
    }
    
    // Создание нового пользователя
    $user = new User();
    $user->setName('John Doe');
    $user->setEmail('john@example.com');
    
    $entityManager->persist($user);
    $entityManager->flush();
    
    // Получение всех пользователей
    $userRepository = $entityManager->getRepository(User::class);
    $users = $userRepository->findAll();
    
    // Обновление пользователя
    $user = $userRepository->find(1);
    $user->setEmail('newemail@example.com');
    $entityManager->flush();
    
    // Удаление пользователя
    $user = $userRepository->find(1);
    $entityManager->remove($user);
    $entityManager->flush();
    

    Сравнение ActiveRecord и Doctrine

    1. Подход к ответственности:

      • ActiveRecord: Модели несут ответственность за данные и бизнес-логику.

      • Data Mapper: Модели чисты и не содержат логики доступа к данным, этим занимается мэппер.

    2. Структура кода:

      • ActiveRecord: Меньше кода, более простой и интуитивно понятный, но менее масштабируемый.

      • Data Mapper: Более сложная структура кода, но более гибкий и масштабируемый подход.

    3. Масштабируемость и поддержка сложной логики:

      • ActiveRecord: Лучше подходит для простых приложений с несложной логикой.

      • Data Mapper: Легче масштабируется и поддерживает сложные бизнес-правила.

    Где используется:

    • ActiveRecord: Ruby on Rails Active Record, Laravel Eloquent.

    • Data Mapper: Doctrine (PHP), Hibernate (Java).

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

Вопрос: Что такое ACID

ACID - это акроним, который описывает четыре основные свойства транзакций в базах данных: Atomicity (атомарность), Consistency (согласованность), Isolation (изоляция) и Durability (долговечность). Рассмотрим каждое из этих свойств подробнее и приведем примеры на MySQL.

Atomicity (Атомарность)

Атомарность гарантирует, что все операции внутри транзакции либо выполняются полностью, либо не выполняются вообще. Если транзакция прерывается на полпути, то все изменения отменяются.

Пример на MySQL:

  • START TRANSACTION;
    UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
    UPDATE accounts SET balance = balance + 100 WHERE account_id = 2;
    COMMIT;

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

    Consistency (Согласованность)

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

    Пример на MySQL:

    CREATE TABLE accounts (
        account_id INT PRIMARY KEY,
        balance DECIMAL(10, 2) CHECK (balance >= 0)
    );
    START TRANSACTION;
    UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
    UPDATE accounts SET balance = balance + 100 WHERE account_id = 2;
    COMMIT;
    

    Согласованность гарантирует, что баланс аккаунта не станет отрицательным.

    Isolation (Изоляция)

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

    Пример на MySQL:

    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
    START TRANSACTION;
    SELECT balance FROM accounts WHERE account_id = 1;
    /* Другие транзакции не могут изменить баланс account_id = 1 до завершения текущей транзакции /
    UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
    COMMIT;
    

    Уровень изоляции SERIALIZABLE обеспечивает максимальную изоляцию, но может снижать производительность.

    Durability (Долговечность)

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

    Пример на MySQL:

    START TRANSACTION;
    UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
    UPDATE accounts SET balance = balance + 100 WHERE account_id = 2;
    COMMIT;
    / Изменения записываются на диск и сохраняются даже в случае сбоя */
    

    После выполнения COMMIT, изменения будут записаны на диск, и даже сбой системы не приведет к их потере.

    Эти примеры демонстрируют, как ACID свойства обеспечивают надежность и целостность данных в базах данных MySQL.

Вопрос: Что такое идемпотентность

Идемпотентность (от лат. idem — тот же и potentia — сила, мощь) в программировании и математике означает свойство операции, при котором многократное применение этой операции эквивалентно однократному применению. Проще говоря, это когда выполнение операции несколько раз подряд не изменяет результат по сравнению с выполнением этой операции один раз.

В программировании идемпотентность часто обсуждается в контексте HTTP-методов и баз данных. Примеры идемпотентных операций включают чтение данных (GET-запросы) и обновление данных (например, PUT-запросы), если они не изменяют состояние сервера при повторном выполнении.

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

  1. Чтение данных (HTTP GET запрос)

    <?php
    // Пример GET запроса, который является идемпотентным
    $url = "https://api.example.com/data";
    $response = file_get_contents($url);
    $data = json_decode($response, true);
    
    print_r($data);

    В этом примере многократное выполнение запроса к одному и тому же URL возвращает одно и то же состояние данных, не изменяя их.

  2. Обновление данных (HTTP PUT запрос)

    <?php
    $url = "https://api.example.com/data/123";
    $data = array("name" => "New Name");
    
    $options = array(
        'http' => array(
            'header'  => "Content-type: application/json\r\n",
            'method'  => 'PUT',
            'content' => json_encode($data),
        ),
    );
    
    $context  = stream_context_create($options);
    $result = file_get_contents($url, false, $context);
    
    echo $result;

    В этом примере отправка PUT-запроса с одними и теми же данными к одному и тому же URL обновляет ресурс, но повторные выполнения запроса не изменят результат, так как данные уже обновлены до указанных значений.

  3. Установка значения переменной

    <?php
    // Пример идемпотентного действия в PHP
    $value = 42;
    
    function setValue(&$var, $newValue) {
        $var = $newValue;
    }
    
    setValue($value, 100);
    setValue($value, 100);
    setValue($value, 100);
    
    echo $value; // 100

    В этом примере многократное выполнение функции setValue с одними и теми же аргументами не изменяет конечный результат после первого вызова.

    Идемпотентные операции важны в системах, где необходимо обеспечить устойчивость к сбоям и повторным запросам, таких как веб-сервисы, REST API и транзакционные системы.

Вопрос: что такое состояние гонки, на примере GO

Состояние гонки (race condition) в языке программирования Go — это ситуация, когда несколько горутин (goroutines) одновременно обращаются к общим данным и, по крайней мере, одна из них модифицирует эти данные. Это может привести к непредсказуемому поведению программы, ошибкам и некорректным результатам.

Пример состояния гонки

Рассмотрим пример кода, где несколько горутин инкрементируют общую переменную:

package main

import (
	"fmt"
	"sync"
)

var counter int

func increment(wg *sync.WaitGroup) {
	defer wg.Done()
	for i := 0; i < 1000; i++ {
		counter++
	}
}

func main() {
	var wg sync.WaitGroup

	for i := 0; i < 10; i++ {
		wg.Add(1)
		go increment(&wg)
	}

	wg.Wait()
	fmt.Println("Final Counter:", counter)
}

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

Обнаружение состояния гонки

Для обнаружения состояния гонки в Go можно использовать встроенный инструмент race detector, который активируется при компиляции или запуске программы с флагом -race.

go run -race main.go

Решение проблемы состояния гонки

Для предотвращения состояния гонки можно использовать механизмы синхронизации, такие как мьютексы (mutexes) или каналы (channels).

Использование мьютекса

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

package main

import (
	"fmt"
	"sync"
)

var counter int
var mu sync.Mutex

func increment(wg *sync.WaitGroup) {
	defer wg.Done()
	for i := 0; i < 1000; i++ {
		mu.Lock()
		counter++
		mu.Unlock()
	}
}

func main() {
	var wg sync.WaitGroup

	for i := 0; i < 10; i++ {
		wg.Add(1)
		go increment(&wg)
	}

	wg.Wait()
	fmt.Println("Final Counter:", counter)
}

В этом примере мьютекс mu блокирует доступ к переменной counter, обеспечивая атомарность операции инкремента.

Использование каналов

Пример с использованием каналов для синхронизации доступа к переменной:

package main

import (
	"fmt"
	"sync"
)

func increment(counter chan int, wg *sync.WaitGroup) {
	defer wg.Done()
	for i := 0; i < 1000; i++ {
		value := <-counter
		value++
		counter <- value
	}
}

func main() {
	var wg sync.WaitGroup
	counter := make(chan int, 1)
	counter <- 0

	for i := 0; i < 10; i++ {
		wg.Add(1)
		go increment(counter, &wg)
	}

	wg.Wait()
	fmt.Println("Final Counter:", <-counter)
}

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

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

Вопрос: SELECT ... FOR UPDATE

В MySQL команда SELECT ... FOR UPDATE используется для выборки строк и их блокировки для последующего обновления. Это полезно в транзакциях, когда нужно избежать состояния гонки (race condition), обеспечивая, что выбранные строки не будут изменены другими транзакциями до завершения текущей транзакции. Вот подробности и примеры использования:

Принцип работы

  1. Выборка и блокировка: SELECT ... FOR UPDATE выбирает строки и устанавливает на них блокировку, предотвращая их изменение другими транзакциями до завершения текущей.

  2. Использование в транзакциях: Обычно используется внутри транзакции для обеспечения целостности данных.

Пример использования

Предположим, у нас есть таблица accounts с колонками id, name и balance.

CREATE TABLE accounts (
    id INT PRIMARY KEY,
    name VARCHAR(100),
    balance DECIMAL(10,2)
);
  1. Начало транзакции:

    START TRANSACTION;
  2. Выборка строки с блокировкой:

    SELECT balance FROM accounts WHERE id = 1 FOR UPDATE;
    
  3. Обновление строки:

    UPDATE accounts SET balance = balance + 100 WHERE id = 1;
    
  4. Завершение транзакции:

    COMMIT;

Полный пример с кодом

-- Начало транзакции
START TRANSACTION;

-- Выборка и блокировка строки
SELECT balance FROM accounts WHERE id = 1 FOR UPDATE;

-- Логика проверки и обновления баланса
UPDATE accounts SET balance = balance + 100 WHERE id = 1;

-- Завершение транзакции
COMMIT;

Важно учитывать

  • Блокировки: Строки, выбранные с помощью SELECT ... FOR UPDATE, блокируются до завершения транзакции (COMMIT или ROLLBACK).

  • Изоляция транзакций: Уровень изоляции транзакции влияет на поведение блокировок. По умолчанию MySQL использует уровень изоляции REPEATABLE READ, который гарантирует, что данные не будут изменены другими транзакциями до завершения текущей.

  • Использование с осторожностью: Необходимо избегать долгих транзакций с SELECT ... FOR UPDATE, так как это может привести к блокировкам и снижению производительности.

Альтернативы

  • LOCK IN SHARE MODE: Альтернативный способ блокировки строк, который позволяет другим транзакциям читать данные, но не изменять их.

    SELECT balance FROM accounts WHERE id = 1 LOCK IN SHARE MODE;

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

Вопрос: есть бесконечный список, как в этом списке найти нужное значение, без усложнение кода. Напиши код на php

В PHP можно создать бесконечный генератор с использованием генераторов и искать нужное значение, например, следующим образом:

<?php
function infiniteGenerator() {
    $i = 0;
    while (true) {
        yield $i++;
    }
}

function findValue($generator, $target) {
    foreach ($generator as $value) {
        if ($value === $target) {
            return $value;
        }
    }
}

$targetValue = 1000;
$generator = infiniteGenerator();
$foundValue = findValue($generator, $targetValue);

echo "Found value: $foundValue\n";

В этом коде:

  1. Функция infiniteGenerator создает бесконечный генератор чисел, начиная с 0.

  2. Функция findValue проходит через значения генератора и сравнивает их с целевым значением. Как только значение найдено, функция возвращает его.

Этот подход не усложняет код и позволяет искать значение в бесконечном списке.

Заключение

Успешное трудоустройство для Senior PHP и Go разработчика требует тщательной подготовки и стратегического подхода. Обновите своё резюме и портфолио, расширьте профессиональные связи, подготовьтесь к собеседованиям, изучите рынок труда и подавайте заявки с персонализированным подходом. Продолжайте учиться и развиваться, и вы обязательно найдете работу своей мечты.

Комментарии (8)


  1. MediaRise Автор
    14.06.2024 12:57

    Допустил ошибку, сравнив Active Record с Doctrine, а не Data mapper pattern паттерн, сорри поправлю


  1. R0bur
    14.06.2024 12:57
    +1

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

    Что-то мне это определение не нравится. Может, оно для мира PHP/Backend? В моём понимании, конкурентность — это способность управлять доступом к ограниченным ресурсам. А то, что описано выше, — это (псевдо)многозадачность.


    1. MediaRise Автор
      14.06.2024 12:57

      соглашусь, предложил альтернативное определение, по проще для понимания


      1. n43jl
        14.06.2024 12:57

        Вопрос немного в сторону: любой ли параллельный код является конкурентным?

        Гугл выдает противоположные ответы:

        • Parallelism implies concurrency by definition

        • An application can be parallel, but not concurrent: if it processes sub-tasks of a single task in parallel.

        Я в первом лагере, так как приведенный пример не конкурентного выполнения таска - это конкурентное выполнение саб-тасков.


        1. R0bur
          14.06.2024 12:57

          Вот пример: рассчитать таблицу умножения для десятичной системы счисления, то есть заполнить матрицу 10 x 10 значениями, равными произведению номера строки на номер столбца (нумерация начинается с единицы). Для простоты решать будем десятью потоками. Каждому даём свой персональный столбец (или строку) — и вперёд. Параллельность есть? Есть. А где конкурентность?


  1. PeopleMax
    14.06.2024 12:57

    Спасибо. Очень пригодилось


  1. vasyakolobok77
    14.06.2024 12:57
    +1

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

    В случае с race condition допущена неточность. В ЯП с многозадачностью на потоках различают race condition и data race.

    Состояние гонки – это когда корректность программы зависит от того, в каком порядке выполняются части кода. Частным случаем является TOCTOU проблема – когда в нескольких потоках делаем некую проверку, а потом исполняем код на основе этой проверки. Например, на балансе лежит 100руб, мы проверяем можем ли купить товар, допустим, за 90руб, и в случае успеха списываем. Если не делать синхронизации и запустить такой код в 2 потоках, то проверка на 100руб > 90руб успешно пройдет в обоих потоках и спишет 180руб, в итоге на балансе станет -80руб.

    Гонка данных в свою очередь – это когда несколько потоков читают переменную, и хотя бы 1 поток пишет. Например, в одном потоке мы бегаем в цикле flag = true; while(flag); а в другом потоке меняет флаг flag = false; Без корректной синхронизации такой код может выполняться вечно.


  1. SerafimArts
    14.06.2024 12:57

    Всё отлично, но от кода на PHP начала 2010х годов (если не раньше) -- глаз дёргается))