Zmienne środowiskowe? A komu to potrzebne?
Pisząc poważną aplikację szybko zdamy sobie sprawę, że "hard-codowanie" pewnych stałych nie jest najlepszym pomysłem. Dane dostępowe do baz danych, czy innych aplikacji - są to rzeczy, które mogą się generalnie zmieniać w czasie. Nie chcemy być zmuszeni do grzebania w kodzie np. dlatego, że baza jest przenoszona na inny serwer. Jest jeszcze jeden problem: można się domyśleć, że na naszym lokalnym środowisku developerskim będziemy używać innej bazy danych niż na serwerze docelowym (produkcyjnym). W dodatku prawdopodobnie po drodze pojawi się jeszcze jakieś środowisko testowe z jeszcze inną bazą; co gorsza może być tych środowisk jeszcze więcej.
Zapisywanie więc takich rzeczy w kodzie jest kompletnie bez sensu. Jak inaczej? Można użyć zmiennych środowiskowych! Jednym ze sposobów na definiowane zmiennych środowiskowych jest wprowadzenie ich jako parametrów wywołania aplikacji, np. tak:
java -jar mojaAplikacja.jar --my.property=HELLO
Przekazywanie jednak wielu zmiennych w ten sposób, jeszcze inaczej dla każdego środowiska może być dość karkołomne. Zapewne można lepiej. A jak? Najlepiej w jakimś zewnętrznym pliku.
Co na to Spring Boot?
Spring Boot daje bardzo przyjemne metody pracy ze zmiennymi środowiskowymi. Jest ogólnie kilka sposobów na osiągnięcie celu. Skupię się jednak na jednym, który moim zdaniem jest najwygodniejszy - pliki .yml. Jak taki plik wygląda?:
spring: profiles: default example: testProperty: testowa_właściwość_default
database:
port: 5432
userName: postgres
host: localhost
password: haslo
databaseName: devdb --- spring: profiles: test example: testProperty: testowa_właściwość_test
database:
host: 10.23.29.1
password: GR5H2#$$^@@
databaseName: testMyAppDb
Jak widać, plik .yml ma strukturę drzewiastą. Jest to niezwykle wygodne do określania struktury naszych zmiennych. Zastanawiacie się, w jaki sposób wstrzyknąć taką konfigurację do naszej aplikacji? Mniej więcej tak łatwo:
@Component @ConfigurationProperties(prefix = "database") public class DatabasePropertiesGetter { private String port; private String host; private String userName; private String databaseName; private String password; //setters! }
Wystarczy więc stworzyć klasę odpowiadającą strukturą danej gałęzi drzewa i oznaczyć ją adnotacją @ConfigurationProperties z pasującym prefiksem. Zadziała to tylko na beanach zarządzanych przez Spring, stąd adnotacja @Component.
W pliku powyższym określiliśmy zmienne domyślne oraz te charakterystyczne dla środowiska testowego. Skąd Spring Boot będzie wiedział, które z nich wstrzyknąć do naszej aplikacji? To również trzeba podać przez odpowiednią zmienną konfiguracyjną: spring.profiles.active. Aby nie zaciemniać głównego pliku .yml, polecam ustawić tę zmienną w inny sposób: przez parametr w linii poleceń, albo w innym pliku.
Zastosowanie w aplikacji
Właśnie, to był wstęp. Teraz zapewne warto byłoby wyjaśnić krok po kroku jak zastosować pliki .yml do konfiguracji naszej aplikacji. Żeby nie zaczynać od zera, posłużmy się aplikacją, którą stworzyliśmy poprzednio (tutaj).
Załóżmy prosty scenariusz: użytkownik chce mieć możliwość sprawdzenia, na jakim środowisku jest aplikacja (może przecież zapomnieć :) ). W tym celu rozbudujmy strukturę plików naszej aplikacji do takiej postaci:
-MyFirstSpringBootApp --config ---application.properties ---application.yml --src ---main ----java -----com ------example -------Environment.java -------HelloController.java -------MainClass.java --pom.xml
Zobaczmy teraz, co się zmieniło.
Folder config - tutaj znajdują się nasze pliki konfiguracyjne. Dlaczego akurat tutaj? Generalnie nie ma to znaczenia, gdzie się znajdą, o ile powiemy Mavenowi, że ma zawartość takiego folderu dołączyć do budowanego jara.
Plik application.properties - jego zawartość jest następująca:
spring.profiles.active=dev
Służy on do ustawienia środowiska, na którym działa aplikacja. Pliki .properties są natomiast alternatywnym do plików .yml sposobem ustawiania zmiennych środowiskowych. Póki co, ustawiamy w tym pliku aktywne środowisku developerskie.
Plik application.yml - definiuje zmienne środowiskowe z podziałem na środowiska. Dla naszego przypadku może wyglądać tak:
spring: profiles: default environment: name: nieznane --- spring: profiles: dev environment: name: dev --- spring: profiles: test environment: name: test --- spring: profiles: prod environment: name: produkcjaOK, czas wprowadzić te dane do aplikacji.
Plik Environment.java - nowa klasa, która będzie odzwierciedlać właściwości środowiska (czyli jak na razie tylko jego nazwę :) ):
package com.example; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Component @ConfigurationProperties(prefix = "environment") public class Environment { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }
Zwykła klasa z jednym polem oraz getterem i setterem dla niego. Dodatkowo mamy adnotację @Component, która tworzy beana springowego, do którego dzięki adnotacji @ConfigurationProperties wstrzykiwana jest wartość zmiennej name. Uwaga: setter, chociaż nie będzie jawnie wywoływany, jest tutaj konieczny. Inaczej pole name nie będzie zainicjalizowane.
Plik HelloController.java - co się zmieniło w naszym kontrolerze?:
package com.example; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.beans.factory.annotation.Autowired; @RestController public class HelloController { @Autowired private Environment env; @RequestMapping(value = "/") public String helloWorld() { return "Hello World
" + "Moja pierwsza aplikacja w Spring Boot"; } @RequestMapping(value = "/whatEnvironmentIsThis") public String whatEnvironment() { return "Aplikacja działa na środowisku " + env.getName(); } }
Doszło nam więc pole typu Environment, w które zostanie wstrzyknięty nasz bean oraz metoda mapująca żądania HTTP pod adres /whatEnvironmentIsThis, która realizuje nasze założenie: zwraca nazwę środowiska.
Plik pom.xml - musimy mavena poinstruować, żeby dodał zawartość katalogu config do generowanego jara. Dzieje się to przez dodanie następującego kodu:
Zaś cały plik wygląda tak:config
4.0.0 com.example SpringBootFirstApp 0.0.1-SNAPSHOT org.springframework.boot spring-boot-starter-parent 1.2.3.RELEASE config org.springframework.boot spring-boot-maven-plugin org.springframework.boot spring-boot-starter-web
Efekty
Czas sprawdzić, czy to wszystko działa. Budujemy więc aplikację komendą mvn clean build i uruchamiamy powstałego jara. Po wejściu w przeglądarce na:
http://localhost:8080/whatEnvironmentIsThis
otrzymujemy następującą odpowiedź:
Aplikacja działa na środowisku dev
Jest to zgodne z prawdą. Spróbujmy teraz to zmienić.
Uruchommy teraz ponownie aplikację, tym razem dodając parametr, który ustawi zmienną spring.profiles.active na wartość "test":
java -jar target/SpringBootFirstApp-0.0.1-SNAPSHOT.jar --spring.profiles.active=test
Strona, jaką zobaczymy pod adresem http://localhost:8080/whatEnvironmentIsThis wygląda tak:
Aplikacja działa na środowisku test
Zmienne podane jako parametry wywołania mają najwyższy priorytet - dlatego to, co wpisaliśmy w konsoli nadpisuje nam wartość zdefiniowaną w pliku application.properties. Gdyby zmodyfikować plik application.properties następująco:
spring.profiles.active=test
i odpalili jara bez dodatkowych parametrów, efekt byłby ten sam.
Na koniec uruchomimy aplikację z nazwą środowiska, które nie zostało określone w pliku .yml. Powinno to skutkować załadowaniem domyślnych wartości, a więc tych zdefiniowanych pod profilem default. Uruchamiamy jara następująco:
java -jar target/SpringBootFirstApp-0.0.1-SNAPSHOT.jar --spring.profiles.active=dontknow
Skutek tego jest zgodny z oczekiwaniem - teraz nasza strona wygląda tak:
Aplikacja działa na środowisku nieznane
Pola listowe
W pliku .yml możemy również definiować pola listowe. Listy można definiować następująco:
myProperty: servers: - serv1.abb.com - serv2.abb.com
Taką listę serwerów możemy przypiąć do naszej aplikacji tworząc klasę tego typu:
@Component @ConfigurationProperties(prefix = "myProperty") public class MyClass { private List< String > servers; public List< String > getServers() { return servers; } public void setServers(List< String > servers) { this.servers = servers; } }
Edycja zmiennych po zbudowaniu aplikacji
Nasze pliki konfiguracyjne - application.properties i application.yml są pakowane do wynikowego jara - nie ma problemu, żeby w archiwum otworzyć taki plik i wyedytować go. Po ponownym uruchomieniu, aplikacja będzie działać z uaktualnionymi zmiennymi środowiskowymi.
Podsumowanie
Temat zmiennych środowiskowych przewija się w każdej poważniejszej aplikacji. Mam nadzieję, że ten post przyda się, aby w Waszych aplikacjach do utrzymania porządków w konfiguracji.
Brak komentarzy:
Prześlij komentarz