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!