Приветствуем вас, Хабр.
В течение минувшего года мы серьёзно прорабатывали тему инженерии данных (Data Engineering), поскольку остались очень довольны читательским интересом к вышедшей у нас книге «Основы инженерии данных: как создавать надёжные системы обработки данных» Джо Риса и Мэтта Хоусли (оригинал - издательство "O'Reilly"). В январе вышла её допечатка.

Кроме того, у нас уже переведена и ушла в редактуру более продвинутая книга, также от O'Reilly, написал которую Бартош Конечны (Bartosz Konieczny); она называется «Data Engineering Design Patterns: Recipes for Solving the Most Common Data Engineering Problems».

Мы предлагаем вам познакомиться с переводом статьи господина Конечны, в котором он излагает идею своей книги и на нескольких примерах демонстрирует, как можно переосмыслить в области инженерии данных классические паттерны проектирования, сформулированные в середине 90-х «бандой четырёх» (Гаммой, Хелмом, Джонсоном и Влиссидесом).
Знаменитые паттерны GoF («банды четырёх») без труда понятны программисту. Можно почитать любую из множест��а книг об этих паттернах и проанализировать их реализации на интересующем вас языке программирования. Но они могут быть не так очевидны для специалиста, привыкшего к исследованию данных, но не столь сильного в программной инженерии. Если вы — именно такой исследователь, интересующийся, зачем вам эти паттерны проектирования, то надеюсь, что сегодняшняя статья поможет вам в этом немного разобраться.
Чтобы объяснить паттерны проектирования, мне не понадобится ни строки кода. Я поступлю иначе — попробую соотнести их с типичными компонентами данных, которые вы как дата-инженер, вероятно, уже встречали на практике.
Синглтон — ведущий датасет
С ним проще всего! Синглтон определяет объект, который должен быть создан строго в одном экземпляре. То есть, во всём вашем приложении будет только один экземпляр синглтона. В мире данных именно «один» и «экземпляр» являются ключевыми характеристиками синглтона. Сначала я хотел рассмотреть здесь концепцию главного датасета из лямбда-архитектуры, но позже обнаружил, что этот архитектурный компонент входит в состав более глобальной концепции, именуемой «управление мастер-данными» (MDM).
MDM — это набор процедур, составленный для того, чтобы гарантировать, что ведущий (он же «справочный») датасет является точным и самосогласованным в масштабах всей организации. Выражаясь менее формально, можно сказать, что MDM гарантирует, что все получатели данных будут получать для описания конкретной концепции (данные, продукт) один и тот же набор информации. Для обеспечения согласованности далее обязательно брать такое представление только из ведущего датасета. Поэтому его логично сравнить с синглтоном.
Фасад — представление
Следующий паттерн проектирования, соотносимый с элементами данных и наверняка попадавшийся вам при работе с реляционными базами данных — это представление (view). Как оно соотносится с паттернами проектирования? Идея представления очень хорошо укладывается в паттерн «фасад».
Фасад нужен для того, чтобы скрывать сложность расположенной за ним архитектуры. То же касается и представлений, создавать которые можно разными способами, например, при операциях UNION или JOIN. Если все пользователи представления должны «запомнить» определение этого представления, то эволюционировать оно будет с трудом, поскольку потребует синхронизированно обновлять и это определение, и все клиенты. Но, благодаря такой дополнительной вставной абстракции представления, владелец данных (вы) может свободно менять определение, не тревожа потребителей. Разумеется, лишь при условии, что после обновления вы представляете ту же самую структуру, что и до него.
Снимок — версии Delta Lake
Далее рассмотрим паттерн проектирования под названием «Снимок» (Memento). Сущность его в том, чтобы обеспечить восстановимость состояния. Отличные примеры операции такого рода — операции «Отмена» и «Повтор» в текстовых редакторах. Как обычно, в мире данных есть множество примеров такого рода.
В качестве примеров Снимка в мире данных удобно привести версии объектного хранилища (например, S3), а также версионирование новы�� ACID-совместимых форматов файлов, таких как Delta Lake. Я по-прежнему в поиске корректного определения этих форматов (Delta Lake, Hudi, Iceberg). Если вы знаете более точную формулировку, чем «ACID-совместимый» — расскажите о ней.
Наблюдатель — потребитель потоковых данных
Тут всё просто. Изучая определение наблюдателя, легко найти ему соответствие в мире данных. Дело в том, что в таком определении очень часто (как ключевое слово) упоминается «публикация-подписка».
В качестве примера наблюдателя в мире данных можно рассмотреть потребитель потоковых данных, например, потребитель Apache Kafka. Сначала он потребляет все записи, произведённые по заданному топику. Говорят, что потребитель — это наблюдатель темы (топика). Как только потребитель больше не будет заинтересован получать события по заданному топику — можно просто остановиться и отписаться от него.
Разумеется, пример с потоковыми данными адекватен до тех пор, пока в топике содержатся все те события, в которых заинтересован заданный потребитель. В качестве альтернативного примера можно привести привязки в RabbitMQ.
Адаптер — Apache Spark
Если вы когда-нибудь сталкивались с такой проблемой, что нужно срочно подзарядить устройство, а зарядка нашлась с неподходящим разъёмом, то вы вполне понимаете смысл паттерна «адаптер». Идея в использовании такой абстракции, благодаря которой удаётся подружить несовместимые интерфейсы и заставить их работать вместе. Итак, что из мира данных можно сравнить с вилкой (которая подходит или не подходит к розетке)?
Конечно же, это Apache Spark! Точнее, его API источник�� данных. Представьте, как осложнилась бы практическая инженерия данных, если бы не было возможности в рамках одного инструмента читать и обрабатывать разные форматы данных. Кошмар, правда? К счастью, есть Apache Spark, в котором предоставляется унифицированная модель обработки данных, и всё представляется как единственная обрабатываемая абстракция.
Цепочка обязанностей - конвейер ETL
Наконец, рассмотрим паттерн проектирования под названием «цепочка обязанностей», в котором воплощён следующий принцип программной инженерии: композиция предпочтительнее наследования. Идея в том, чтобы создать цепочку классов/функций, которые будут последовательно вызываться для обработки конкретного параметра, поступившего на вход. В 2017 году я написал, как эта концепция работает применительно к Apache Spark UDF, но в мире данных есть и другие примеры такого рода.
Мне этот паттерн во многом напоминает конвейер ETL, где поступившая на вход информация преобразуется шаг за шагом, пока на последнем этапе не преобразуется в вывод.
Другая важная характеристика цепочки обязанностей наряду с таким последовательным устройством — это возможность предусмотреть в цепочке дескриптор, который позволит пропустить запрос, например, если неизвестно, как его обработать. Подобные вещи достижимы при работе с конвейерами ETL, в которых применяется ветвление.
В этой статье описаны лишь некоторые паттерны инженерии данных, на самом деле, их гораздо больше. Но, если вы не очень хорошо ориентируетесь в паттернах, то вам пригодится умение подбирать любому паттерну аналогию в мире данных. Так будет значительно проще разобраться в любой задаче.
