W dzisiejszych czasach ciężko wyobrazić sobie aplikację, która nie przetwarzałaby i przechowywałaby danych do niej dostarczanych. Najczęściej stosowanym pojemnikiem na informacje jest baza danych, którą należy odpowiednio przygotować przed jej użyciem. Wraz z upływem czasu, gdy oprogramowanie będzie rozwijane, niezbędne będzie dokonywanie zmian w schemacie bazy danych. Powszechnie wykorzystuje się do tego celu skrypty SQL zawierające aktualizujące instrukcje. Jednak to rozwiązanie jest mocno uzależnione od czynnika ludzkiego, ponieważ osoba za to odpowiedzialna może wykonać dany skrypt kilka razy lub też zapomnieć w ogóle go uruchomić. Możliwy jest także przypadek, że z jakiejś przyczyny procedura się zatrzyma w trakcie i ciężko będzie dojść co się już wykonało a co nie. Z tego właśnie powodu postanowiono pochylić się nad tym problem i wymyślono narzędzia do zarządzania zmianami na bazie danych. Jednym z nich jest właśnie Liquibase, o którym chciałbym pokrótce napisać w tym artykule.
Czy jest Liquibase?
Sam Liquibase został napisany w Javie i jego głównym zadaniem jest śledzenie, zarządzanie i aplikowanie zmian w schemacie bazy danych. Jego początki sięgają 2006 roku i aktualnie dalej jest rozwijany, obecnie znajduje się w wersji 4.2.2 (grudzień 2020r). Twórcy zapewniają, że Liquibase pozwala w łatwy sposób monitorować zmiany w bazie danych zwłaszcza w środowisku pracy Agile. Jego głównymi funkcjonalnościami są:
- elastyczna i prosta zmiana schematu - instrukcje mogą być zapisane w formie SQL, XML, YAML czy JSON
- automatyczne generowanie skryptów SQL - można je zweryfikować tuż po wygenerowaniu, czy na pewno otrzymaliśmy takie zapytania SQL jak chcieliśmy
- powtarzalne migracje - powtarzalność wykonania na różnych środowiskach, możemy zdecydować, które instrukcje mogą być ponownie wykonywanie podczas uruchomienia a które nie
- integracja w wieloma narzędziami i bazami danych
- bezproblemowe wycofanie zmian - automatycznie lub poprzez niestandardowe instrukcje
- logika wykonania zależna od kontekstu - możliwe jest dostosowanie wykonywanych skryptów
- monitorowanie w czasie rzeczywistym - darmowy dostęp do Liquibase Hub
No dobrze, ale jak faktycznie może wyglądać plik obsługiwany przez Liquibase? Poniżej przygotowałem te same instrukcje w formacie YAML jak i XML.
Wersja YAML:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
databaseChangeLog:
- changeSet:
id: create-table-users
author: devcezz
preConditions:
- onFail: MARK_RAN
not:
tableExists:
tableName: users
changes:
- createTable:
columns:
- column:
autoIncrement: true
constraints:
nullable: false
primaryKey: true
primaryKeyName: user_pkey
name: id
type: BIGINT
- column:
constraints:
nullable: false
name: name
type: VARCHAR(250)
- column:
constraints:
nullable: false
name: age
type: BIGINT
tableName: users
- changeSet:
id: add-created-column-to-users
author: devcezz
preConditions:
- onFail: MARK_RAN
tableExists:
tableName: users
changes:
- addColumn:
tableName: users
columns:
- column:
constraints:
nullable: false
name: createdAt
type: DATETIME
defaultValueDate: CURRENT_TIMESTAMP
Wersja XML:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.8.xsd">
<changeSet id="create-table-users"
author="devcezz">
<preConditions onFail="MARK_RAN">
<not>
<tableExists tableName="users"/>
</not>
</preConditions>
<createTable tableName="users">
<column name="id"
type="BIGINT"
autoIncrement="true">
<constraints nullable="false"
primaryKey="true"
primaryKeyName="user_pkey"/>
</column>
<column name="name"
type="VARCHAR(250)">
<constraints nullable="false"/>
</column>
<column name="age"
type="BIGINT">
<constraints nullable="false"/>
</column>
</createTable>
</changeSet>
<changeSet id="add-created-column-to-users"
author="devcezz">
<preConditions onFail="MARK_RAN">
<tableExists tableName="users"/>
</preConditions>
<addColumn tableName="users">
<column name="createdAt"
type="DATETIME"
defaultValueDate="CURRENT_TIMESTAMP">
<constraints nullable="false"/>
</column>
</addColumn>
</changeSet>
</databaseChangeLog>
Powyższe przykłady sprowadzają się do dobrze znanych komend SQL. Ich zadaniem jest utworzenie tabeli users zawierającą kolumny id, name i age, a następnie dodanie jeszcze jednej kolumny z datą utworzenia użytkownika.
1
2
3
4
5
6
7
8
CREATE TABLE `users` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(250) NOT NULL,
`age` bigint NOT NULL,
PRIMARY KEY (`id`)
);
ALTER TABLE `users` ADD `createdAt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP;
Instrukcje w Liquibase faktycznie są dłuższe niż w SQL, ale jest to cena, którą moim zdaniem warto zapłacić. Zyskujemy w ten sposób pełną automatyzację podczas uruchamiania aplikacji w np. Springu. Nie martwimy się o to czy ktoś zapomni wykonać odpowiednie skrypty aktualizujące posiadając przy tym historię zmian w kolejności chronologicznej. Ogromnym plusem jest też fakt, że każdy changeset zadziała tak samo na różnych silnikach baz danych.
Podsumowanie
Jak zawsze zachęcam do eksperymentowania ze wszystkimi dostępnymi rozwiązaniami na rynku. Może zapoznanie się z którymś narzędziem pozwoli Ci błysnąć w pracy i wywrzeć wrażenie na przełożonym, czy też ułatwi Ci pracę nad własnym projektem. Ja osobiście jestem na początku drogi zapoznania się z przeróżnymi mechanizmami oferowanymi w naszej branży. Liquibase poznałem w pracy i zacząłem go używać do swojego projektu, który rozwijam w ramach artkułów na ‘Przepisz swój kod na nowo!’. Muszę przyznać, że jest to dla mnie ciekawe rozwiązanie, które na pewno ułatwia pracę poprzez automatyzację niektórych zadań. Miałeś bądź miałaś może styczność z podobnym narzędziem? Może masz jakieś inne rozwiązania, które automatyzują pewne procesy? Zapraszam do dyskusji!