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

В данной статье мы рассмотрим особенности хранения транзакций в основных языках.

Плюсы и минусы хранения транзакций

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

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

Python, Java, C# и Go: особенности встраивания транзакций

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

Если приложение усложняется, то в однопоточных языках, таких как PHP и Python, можно использовать глобальные переменные, так как эти языки не подвержены проблемам с параллельным выполнением кода. Однако использование глобальных переменных также может привести к трудностям, в том числе, к неопределенному поведению, сложностям при тестировании и затруднению отслеживания зависимостей между различными частями программы. Если говорить о Python, менеджеры транзакций часто встроены в ORM (например, Django). Обычно транзакции хранятся в глобальной переменной из-за того, что Python чаще всего используется в однопоточном режиме. Этот подход также используется в PHP с ORM, такими как Doctrine и Eloquent.

В многопоточных языках транзакция привязывается к потоку, но тут возникает критичный вопрос: как именно это делать? В Java и C# это осуществляется через ID потока или, если быть точными, через Thread Local Storage, а в Go нет ID горутины, поскольку это не соответствует идеологии языка. Если есть ID потока или горутина, значит, при параллельном программировании есть шанс, что разработчик захочет все сложить в один поток, и только маленькие кусочки отдать в другие потоки, не используя всю мощь параллельного программирования.

В качестве примера можно привести комплексный проект по перепродаже мобильных устройств в «Авито». Для программирования был выбран Go как простой, быстрый и высокопроизводительный язык. Однако недостатки Go включают отсутствие некоторых возможностей других языков и небольшое количество опытных разработчиков. Чтобы решить эти проблемы и достичь удобства работы с транзакциями, как в Java и С#, был использован контекст (его адаптировали под хранение транзакции) и создана библиотека с открытым исходным кодом.

Сравнение способов хранения данных

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

Существует несколько способов представления данных в приложении.

  1. Анемичная модель. В анемичной модели все данные представлены в виде плоских структур данных (например, классы сеттеров и геттеров без логики). Это позволяет легко сохранять и извлекать данные из базы данных, но при этом может создавать проблемы с обработкой бизнес-логики и поддерживаемостью кода.
  2. Active Record. Active Record — это паттерн проектирования, который объединяет данные и методы доступа к базе данных в одном классе (или объекте). Однако с увеличением сложности приложения Active Record может стать неудобным из-за смешения слоев и недостаточного разделения ответственностей.
  3. Доменная модель. Доменная модель создает сеть взаимосвязанных объектов, где каждый объект представляет собой какое-то значимое явление: крупное, как целая корпорация, или маленькое, как одна строка в форме заказа.

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

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

Илья Сергунин, ИТ-эксперт