настройка приложений баз данных

Универсально храним настройки приложения через IConfiguration

настройка приложений баз данных. tf6jvphlyvlsfqvz9bt4bwyec9c. настройка приложений баз данных фото. настройка приложений баз данных-tf6jvphlyvlsfqvz9bt4bwyec9c. картинка настройка приложений баз данных. картинка tf6jvphlyvlsfqvz9bt4bwyec9c.

Определяем задачи

В ASP.Net Core приложениях появилась возможность работать с настройками приложения через интерфейс IConfiguration. По работе с ним написано не мало статей. Эта статья расскажет об опыте использования IConfiguration для хранения настроек нашего приложения, таких как настройки подключение к LDAP серверу, к SMTP серверу и т.п. Цель — настроить существующий механизм работы с конфигурациями приложения на работу с БД. В этом материале вы не найдете описание стандартного подхода использования интерфейса.

Архитектура приложения построена у нас по DDD в сцепке с CQRS. К тому же, мы знаем, что объект интерфейса IConfiguration хранит все настройки в виде пары “ключ-значение”. Поэтому, мы вначале описали некую сущность настроек на домене в таком виде:

В качестве ORM в проекте используется EF Core. А за миграции отвечает FluentMigrator.
Добавляем новую сущность в наш контекст:

Далее для нашей новой сущности нужно описать конфигурацию EF:

И написать миграцию для этой сущности:

А где же тут упомянутый IConfiguration?

Применяем интерфейс IConfigurationRoot

В нашем проекте есть api приложение построенное на ASP.NET Core MVC. И по умолчанию, мы используем IConfiguration для стандартного хранения настроек приложения, например подключения к БД:

Эти настройки хранятся по умолчанию в переменных среды окружения:

И по идеи, мы можем использовать этот объект для хранения задуманных настроек, но тогда они будут пересекаться с общими настройками самого приложения (как упоминалось выше — подключения к БД)

Для того чтобы отделить подключенные объекты в DI, решили использовать дочерний интерфейс IConfigurationRoot:

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

Однако, наш объект в контейнере ничего не знает про нашу сущность в домене и как работать с БД.

настройка приложений баз данных. aryq7ut198vmkakyvr9rzule5fc. настройка приложений баз данных фото. настройка приложений баз данных-aryq7ut198vmkakyvr9rzule5fc. картинка настройка приложений баз данных. картинка aryq7ut198vmkakyvr9rzule5fc.

Описываем нового провайдера конфигурации

Напомним, что наша задача — хранить настройки в БД. А для этого нужно описать нового провайдера конфигурации IConfigurationRoot, унаследованного от ConfigurationProvider. Для корректной работы нового провайдера, мы должны описать метод чтения из БД — Load() и метод записи в БД — Set():

Далее, необходимо описать новый source для нашей конфигурации, который реализует IConfigurationSource:

И для простоты, добавляем расширение к IConfigurationBuilder:

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

Что же нам дали наши манипуляции с новым провайдером?

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

Для начала определим некую модель Dto, которая будет транслироваться клиенту нашего приложения, например для хранения настроек подключения к ldap:

Из “коробки” IConfiguration хорошо умеет записывать и читать один экземпляр объекта. А для работы с коллекцией нужны небольшие доработки.

Для хранения нескольких однотипных объектов, мы написали расширение для IConfigurationRoot:

Таким образом, мы можем работать с несколькими экземплярами наших настроек.

Пример записи настроек в БД

Как было упомянуто выше, в нашем проекте используется подход CQRS. Для записи настроек опишем простую команду:

А затем и обработчик нашей команды:

В итоге мы одной строчкой можем записывать данные наших настроек ldap в БД в соответствии с описанной логикой.

В БД же наши настройки выглядят так:

настройка приложений баз данных. image loader. настройка приложений баз данных фото. настройка приложений баз данных-image loader. картинка настройка приложений баз данных. картинка image loader.

Пример чтения настроек из БД

Для чтения настроек ldap мы напишем простой запрос:

А затем и обработчик нашего запроса:

Как видим из примера, с помощью метода Bind, мы наполняем наш объект ldapSettings данными из БД — по названию LdapSettingsDto мы определяем ключ (секцию) по которому нужно получить данные и далее происходит вызов метода Load, описанного в нашем провайдере.

А что далее?

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

Мы надеемся, что наше решение вам будет полезно и вы поделитесь с нами вашими вопросами и замечаниями.

Источник

Создание простого приложения для работы с данными с помощью ADO.NET

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

С целью упрощения код не включает обработку исключений для выполнения в рабочей среде.

Предварительные требования

Для создания приложения вам потребуются следующие компоненты.

SQL Server Express LocalDB. если у вас нет SQL Server Express LocalDB, его можно установить на странице загрузки SQL Server Express.

Настройка образца базы данных

Создайте образец базы данных, выполнив следующие действия.

щелкните правой кнопкой мыши подключения к данным и выберите команду создать новую базу данных SQL Server.

В текстовом поле имя сервера введите (LocalDB) \mssqllocaldb.

В текстовом поле имя новой базы данных введите Sales, а затем нажмите кнопку ОК.

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

Щелкните правой кнопкой мыши подключение к данным о продажах и выберите создать запрос.

Откроется окно редактора запросов.

По истечении короткого времени выполнение запроса завершается и создаются объекты базы данных. База данных содержит две таблицы: Customer и Orders. Эти таблицы изначально не содержат данных, но их можно добавить при запуске создаваемого приложения. База данных также содержит четыре простые хранимые процедуры.

Создание форм и добавление элементов управления

Создайте проект для приложения Windows Forms и назовите его SimpleDataApp.

Visual Studio создает проект и несколько файлов, включая пустую форму Windows Forms с именем Form1.

Добавьте две формы Windows Forms в проект, чтобы он включал три формы, и назначьте им следующие имена:

Навигация

NewCustomer

FillOrCancel

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

Элементы управления «группа» и «надпись» обеспечивают большую ясность, но не используются в коде.

Форма навигации

настройка приложений баз данных. simpleappnav. настройка приложений баз данных фото. настройка приложений баз данных-simpleappnav. картинка настройка приложений баз данных. картинка simpleappnav.

Элементы управления формы навигацииЭлемент Property
КнопкаName = btnGoToAdd
КнопкаName = btnGoToFillOrCancel
КнопкаName = btnExit

Форма NewCustomer

настройка приложений баз данных. simpleappnewcust. настройка приложений баз данных фото. настройка приложений баз данных-simpleappnewcust. картинка настройка приложений баз данных. картинка simpleappnewcust.

Элементы управления формы NewCustomerЭлемент Property
TextBoxName = txtCustomerName
TextBoxName = txtCustomerID

Readonly = True

КнопкаName = btnCreateAccount
NumericUpDownDecimalPlaces = 0

Name = numOrderAmount

DateTimePickerFormat = Short

Name = dtpOrderDate

КнопкаName = btnPlaceOrder
КнопкаName = btnAddAnotherAccount
КнопкаName = btnAddFinish

Форма FillOrCancel

настройка приложений баз данных. simpleappcancelfill. настройка приложений баз данных фото. настройка приложений баз данных-simpleappcancelfill. картинка настройка приложений баз данных. картинка simpleappcancelfill.

Элементы управления формы FillOrCancelЭлемент Property
TextBoxName = txtOrderID
КнопкаName = btnFindByOrderID
DateTimePickerFormat = Short

Name = dtpFillDate

DataGridViewName = dgvCustomerOrders

RowHeadersVisible = False

КнопкаName = btnCancelOrder
КнопкаName = btnFillOrder
КнопкаName = btnFinishUpdates

Сохранение строки подключения

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

В списке тип выберите (строка подключения).

В списке область выберите приложение.

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

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

Написание кода для форм

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

Форма навигации

Форма навигации открывается при запуске приложения. Кнопка Добавить учетную запись открывает форму NewCustomer. Кнопка Выполнение или отмена заказов открывает форму FillOrCancel. Кнопка Выход закрывает приложение.

Преобразование формы навигации в начальную форму

При использовании C# в обозревателе решений откройте файл Program.cs и измените строку Application.Run на следующую: Application.Run(new Navigation());

Создание автоматически создаваемых обработчиков событий

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

Добавление кода для логики формы навигации

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

Форма NewCustomer

Создание автоматически создаваемых обработчиков событий

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

Добавление кода для логики формы NewCustomer

Чтобы завершить логику формы NewCustomer, выполните следующие действия.

Перенесите System.Data.SqlClient пространство имен в область, чтобы не указывать полные имена его членов.

Добавьте в класс некоторые переменные и вспомогательные методы, как показано в следующем коде.

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

Форма FillOrCancel

Создание автоматически создаваемых обработчиков событий

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

Добавление кода для логики формы Филлорканцел

Чтобы завершить логику формы Филлорканцел, выполните следующие действия.

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

Добавьте в класс переменную и вспомогательный метод, как показано в следующем коде.

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

Тестирование приложения

Нажмите клавишу F5 для сборки и тестирования приложения после написания кода для каждого обработчика события нажатия кнопки и общего кода программы.

Источник

Как я создаю базу данных для своих приложений

Если в нашем приложении больше 5 таблиц, то уже было бы не плохо использовать какой-нибудь инструмент для визуального проектирования архитектуры БД. Поскольку для меня это хобби, то и использую я абсолютно бесплатный инструмент под названием Oracle SQL Developer Data Modeler (скачать его можно тут).

настройка приложений баз данных. image loader. настройка приложений баз данных фото. настройка приложений баз данных-image loader. картинка настройка приложений баз данных. картинка image loader.

Данная программа позволяет визуально рисовать таблицы, и строить взаимосвязи с ними. Многие ошибки проектирования архитектуры БД можно избежать при таком подходе проектирования (это я уже вам говорю как профессиональный программист БД). Выглядит это примерно так:

настройка приложений баз данных. image loader. настройка приложений баз данных фото. настройка приложений баз данных-image loader. картинка настройка приложений баз данных. картинка image loader.

Спроектировав саму архитектуру, приступаем к более нудной части, заключающийся в созданий sql кода для создания таблиц. Для помощи в этом вопросе, я уже использую инструмент под названием SQLiteStudio (его в свою очередь можно скачать тут тут).

настройка приложений баз данных. image loader. настройка приложений баз данных фото. настройка приложений баз данных-image loader. картинка настройка приложений баз данных. картинка image loader.

Данный инструмент является аналогом таких известных продуктов как SQL Naviagator, Toad etc. Но как следует из названия, заточен он под работу с SQLite. Он позволяет визуально создать БД и получить DDL код создаваемых таблиц. Кстати, он также позволяет создавать представления (View), которые вы тоже при желании можете использовать в своем приложении. Не знаю насколько правильный подход использования представлений в программах для Android, но в одном из своих приложений я использовал их.

настройка приложений баз данных. image loader. настройка приложений баз данных фото. настройка приложений баз данных-image loader. картинка настройка приложений баз данных. картинка image loader.

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

настройка приложений баз данных. image loader. настройка приложений баз данных фото. настройка приложений баз данных-image loader. картинка настройка приложений баз данных. картинка image loader.

Теперь давайте посмотрим на код внутри моего DBHelper который я использую в своих проектах. Сначала переменные класса и конструктор (тут без каких либо неожиданностей):

Теперь метод onCreate и тут становится уже интереснее:

Логически он разделен на два цикла, в первом цикле я получаю список SQL — инструкций для создания БД и затем выполняю их, во втором цикле я уже заполняю созданные ранее таблицы начальными данными. И так, шаг первый:

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

С заполнением таблиц все веселей. Таблицы у меня заполняются не только жестко заданными значениями, но также значениями из ресурсов и UUID ключами (я надеюсь когда-нибудь прийти к сетевой версии своей программы, что бы мои пользователи могли работать с общими данными). Сама структура файлов с начальными данными выглядит так:

настройка приложений баз данных. image loader. настройка приложений баз данных фото. настройка приложений баз данных-image loader. картинка настройка приложений баз данных. картинка image loader.

Несмотря на то, что файлы у меня имеют расширение sql, внутри не sql код а вот такая штука:

prioritys
pri_id:UUID:UUID
pri_object:string:object_task
pri_name:string:normal
pri_color:color:colorGreen
pri_default:int:1
prioritys
pri_id:UUID:UUID
pri_object:string:object_task
pri_name:string:hold
pri_color:color:colorBlue
pri_default:int:0
prioritys
pri_id:UUID:UUID
pri_object:string:object_task
pri_name:string:important
pri_color:color:colorRed
pri_default:int:0
prioritys
pri_id:UUID:UUID
pri_object:string:object_project
pri_name:string:normal
pri_color:color:colorGreen
pri_default:int:1
prioritys
pri_id:UUID:UUID
pri_object:string:object_project
pri_name:string:hold
pri_color:color:colorBlue
pri_default:int:0
prioritys
pri_id:UUID:UUID
pri_object:string:object_project
pri_name:string:important
pri_color:color:colorRed
pri_default:int:0

Структура файла такая: я выполняю вызов функции split(«:») применительно к строчке и если получаю что ее размер равен 1 то значит это название таблицы, куда надо записать данные. Иначе это сами данные. Первое поле это название поля в таблице. Второе поле тип, по которому я определяю что мне надо в это самое поле записать. Если это UUID — это значит мне надо сгенерировать уникальное значение UUID. Если string значит мне надо из ресурсов вытащить строковое значение. Если color, то опять-таки, из ресурсов надо вытащить код цвета. Если int или text, то я просто преобразую данное значение в int или String без каких либо телодвижений. Сам код выглядит вот так:

Источник

Работа с базами данных глазами разработчика

настройка приложений баз данных. . настройка приложений баз данных фото. настройка приложений баз данных-. картинка настройка приложений баз данных. картинка .

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

Написание SQL миграции → написание кода → тестирование → релиз → мониторинг.

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

Поскольку мы в компании работаем с PostgreSQL, а серверный код пишем на Java, то примеры будут основаны на этом стеке, хотя большинство идей не зависят от используемой БД и языка программирования.

SQL-миграция

Первый этап разработки после проектирования – это написание SQL-миграции. Основной совет – не проводите никаких ручных изменений схемы данных, а всегда делайте это через скрипты и храните их в одном месте.

У нас в компании разработчики сами пишут SQL-миграции, поэтому все миграции хранятся в репозитории с основным кодом. В некоторых компаниях изменением схемы занимаются администраторы БД, в таком случае реестр миграций находится где-то у них. Так или иначе такой подход приносит следующие преимущества:

Мы используем flyway, поэтому дальше будет немного информации о нем:

Когда Java-код устаревает, миграция может быть удалена, чтобы не плодить legacy (сам Java-класс миграции остаётся, но внутри он пустой). У нас это может произойти не ранее, чем через месяц после вывода миграции на production – мы считаем, что это достаточное время для того, чтобы все тестовые окружения и локальные среды разработчиков обновились. Стоит отметить, что поскольку Java-миграции используются только для DML, то их удаление никак не влияет на создание новых БД с нуля.

Важный нюанс для тех, кто использует pg_bouncer

Flyway во время проведения миграции накладывает блокировку для предотвращения одновременного выполнения нескольких миграций. Упрощенно это работает так:

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

Написание кода

Миграция создана, теперь пишем код.

Можно выделить 3 подхода для работы с БД со стороны приложения:

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

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

DSL-библиотеки

Примерно посередине между этими подходами находится ещё один, который заключается в использовании DSL-библиотек (jOOQ, Querydsl и др.). Они, как правило, гораздо легковеснее, чем ORM, но более удобны, чем полностью ручная работа с БД. Использование DSL-библиотек не так распространено, поэтому в этой статье кратко рассмотрим именно этот подход.

Речь пойдёт про одну из библиотек — jOOQ. Что она предлагает:

При желании можно использовать plain sql:

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

jOOQ Record и POJO

BookRecord в примере выше является оберткой над строкой в таблице book и реализует паттерн active record. Поскольку этот класс являются частью слоя доступа к данным (к тому же конкретной его реализации), то вы, возможно, не хотели бы передавать его в другие слои приложения, а использовать какой-то свой pojo-объект. Для удобства конвертации record pojo jooq предлагает несколько механизмов: автоматические и ручной. В документации по ссылкам выше есть разнообразные примеры их использования при чтении, но нет примеров для вставки новых данных и обновления. Восполним этот пробел:

Как можно увидеть, все достаточно просто.

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

Также jooq может генерировать DAO классы с набором базовых методов для упрощения работы с данными таблицы и уменьшения объема ручного кода (это очень похоже на Spring Data JPA):

Мы в компании не используем автогенерацию DAO-классов – генерируем только обертки над объектами БД, а запросы пишем сами. Генерация оберток происходит каждый раз при пересборке отдельного мавен-модуля, в котором хранятся миграции. Чуть далее будут детали о том, как это реализовано.

Тестирование

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

Тоже самое касается и вопроса классификации тестов. В этой статье предлагается использовать следующий вариант разделения:

Интеграционные тесты в отличии от unit-тестов проверяют взаимодействие нескольких модулей между собой. Работа с БД – хороший пример, когда интеграционные тесты имеют смысл, потому что очень сложно качественно «замокать» БД, учитывая все её нюансы. Интеграционные тесты в большинстве случаев являются хорошим компромиссом между скоростью выполнения и гарантиями качества при тестировании БД в сравнении с другими видами тестирования. Поэтому в этой статье поговорим подробнее об этом виде тестирования.

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

Интеграционное тестирование

Когда речь заходит об интеграционном тестировании кода, работающего с БД, большинство разработчиков задаётся вопросами: как запускать БД, как инициализировать её состояние начальными данными и как делать это как можно быстрее?

Какое-то время назад достаточно распространённой практикой в интеграционном тестировании было использование h2. Это in-memory БД, написанная на Java, которая имеет режимы совместимости с большинством популярных БД. Отсутствие необходимости установки БД и универсальность h2 сделали её весьма удобной заменой настоящих БД, особенно если приложение не зависит от конкретной БД и использует только то, что входит в стандарт SQL (что бывает далеко не всегда).

Но проблемы начинаются в тот момент, когда вы используете какой-то хитрый функционал БД (или совсем новый из свежей версии), поддержка которого не реализована в h2. Да и в целом, поскольку это «симуляция» конкретной СУБД, то всегда могут быть некоторые отличия в поведении.

Ещё один вариант – использование embedded postgres. Это настоящий Postgres, поставляемый в виде архива и не требующий установки. Он позволяет работать как с обычной версией Postgres.

Есть несколько реализаций, самые популярные от Yandex и openTable. Мы в компании использовали версию от Yandex. Из минусов – он достаточно медленный при запуске (каждый раз происходит распаковка архива и запуск БД – занимает 2-5 секунд в зависимости от мощности компьютера), также есть проблема с отставанием от официальной релизной версии. Ещё сталкивались с проблемой, что после попытки остановки из кода происходила какая-нибудь ошибка и процесс Postgres оставался висеть в ОС – приходилось убивать его вручную.

testcontainers

Третий вариант – использование docker. Для Java существует библиотека testcontainers, которая предоставляет api для работы с docker-контейнерами из кода. Таким образом, любая зависимость в вашем приложении, которая имеет docker-образ, может быть заменена в тестах с помощью testcontainers. Также для для многих популярных технологий есть отдельные готовые классы, которые предоставляют более удобный api в зависимости от используемого образа:

Если для образа нет отдельного класса в testcontainers, то создание контейнера выглядит примерно так:

Если вы используете JUnit4, JUnit5 или Spock, то в testcontainers есть доп. поддержка для этих фреймворков, которая упрощает написание тестов.

Ускорение тестов с testcontainers

Несмотря на то, что переход с embedded postgres на testcontainers ускорил наши тесты за счёт более быстрого запуска Postgres, со временем тесты стали снова замедляться. Причиной этого послужило увеличение количества SQL-миграций, которые flyway выполняет при запуске. Когда количество миграций перевалило за сотню, время их выполнения было порядка 7-8 секунд, что значительно замедляло тесты. Это работало примерно так:

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

Далее немного технических деталей о том, как это реализовать

Docker имеет встроенный механизм для создания нового образа на основе запущенного контейнера с помощью команды commit. Она позволяет кастомизировать образы, например, изменяя какие-либо настройки.

Важный нюанс, что команда не сохраняет данные примонтированных разделов. Но если взять официальный docker-образ Postgres, то директория PGDATA, в которой хранятся данные, располагается в отдельном разделе (чтобы после перезапуска контейнера данные не терялись), следовательно при выполнении commit состояние самой БД не сохраняется.

Решение простое – не использовать раздел для PGDATA, а держать данные в памяти, что для тестов вполне нормально. Есть 2 способа как добиться этого – использовать свой dockerfile (примерно вот такой) без создания раздела, либо переопределить переменную PGDATA при запуске официального контейнера (раздел останется, но использоваться не будет). Второй путь выглядит значительно проще:

Перед выполнением commit рекомендуется выполнить checkpoint для postgres, чтобы сбросить изменения из shared buffers на «диск» (который соответствует переопределенной переменной PGDATA):

Сам коммит выполняется примерно так:

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

Еще пара слов об оптимизации времени сборки

Как уже было сказано ранее, при сборке отдельного мавен-модуля с миграциями помимо прочего выполняется генерация java-оберток над объектами БД. Для этого используется самописный мавен-плагин, который запускается перед компиляцией основного кода и выполняет 3 действия:

Плагин (метод «start»):

Методы save-state и stop реализованы аналогичным образом и поэтому здесь не представлены.

Релиз

Код написан и протестирован – пора релизить. В целом, сложность релиза зависит от следующих факторов:

Размер БД влияет на время миграции – чем больше база, тем больше вероятность, что вам потребуется провести длительную миграцию.

Бесшовность отчасти является результирующим фактором – если релиз проводится с выключением (downtime), то тогда первые 3 пункта не так важны и влияют только на время недоступности приложения.

Если говорить про наш сервис, то это:

Каждый сервер приложения при запуске сверяет версию БД с версиями скриптов, которые есть в исходном коде (в терминах flyway это называется validation). Если они различаются, сервер не будет запущен. Это гарантирует совместимость кода и базы данных. Не может возникнуть такая ситуация, когда, например, код работает с таблицей, которую еще не создали, потому что миграция находится в другой версии сервера.

Но это конечно не решает проблемы, когда, например, в новой версии приложения есть миграция, удаляющая столбец в таблице, который может использоваться в старой версии сервера. Сейчас мы проверяем такие ситуации только на этапе ревью (оно обязательно), но по-хорошему необходимо внедрить доп. этап с такой проверкой в CI/CD-цикл.

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

настройка приложений баз данных. . настройка приложений баз данных фото. настройка приложений баз данных-. картинка настройка приложений баз данных. картинка .

Мониторинг

После релиза цикл разработки не заканчивается. Чтобы понять работает ли новый функционал (и как он работает) необходимо «обкладываться» метриками. Их можно разделить на 2 группы: бизнесовые и системные.

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

Метрики второй группы примерно одинаковы для всех – они определяют техническое состояние сервера: cpu, памяти, сети, БД и пр.

Что конкретно нужно мониторить и как это делать – это тема огромного множества отдельных статей и здесь она затрагиваться не будет. Хочется напомнить лишь самые базовые (даже капитанские) вещи:

определяйте метрики заранее

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

настраивайте автоматические алерты

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

собирайте метрики со всех узлов

Метрик, как и логов, много не бывает. Наличие данных с каждого узла вашей системы (сервер приложения, БД, пулер соединений, балансировщик и пр.) позволяет иметь полную картину о её состоянии, и в случае необходимости вы сможете быстрее локализовать проблему.

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

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *