Dziś będziemy dodawać bazę danych, a właściwie „bazę danych” do naszego projektu. Jest to całkiem dobry moment by przyjrzeć się skąd JLNS zaczyna wyliczać classpath.

Baza danych

Niekoniecznie relacyjna. Wielokrotnie spotkałem się z niezrozumieniem ze strony poganiaczy w dziedzinie składowania danych. Architekci przy wsparciu managerów zazwyczaj forsują rozwiązania relacyjne. Relacyjna baza danych zapewnia ich zdaniem trwałość danych (pewność ich zapisania) oraz spójność ( zapis jest transakcyjny). Rzecz w tym, że bardzo szybko baza danych staje się odbiciem modelu domeny i to 1:1. Do czego to prowadzi?

Po pierwsze do ograniczenia elastyczności modelu domenowego. Jeżeli zachodzi potrzeba większej zmiany w modelu domenowym, np. przeorganizowania encji z powodów biznesowych, to jesteśmy w dupie. Nagle okazuje się, że trzeba przygotować różne dzikie migracje w strukturze tabeli. To pociąga za sobą konieczność przepisania czy to SQLi, czy to mapowiań ORMowych, a na koniec i tak się okazuje, że jest jeszcze cała masa procedur, triggerów i jobów bazodanowych, które nagle tracą sens. A my tylko chcieliśmy wynieść jeden z VO jakiegoś zagłębienia do głównej encji.

Po drugie wszelkie problemy z wydajnością, które wynikają z niedostosowania modelu relacyjnego do pracy w świecie obiektowym, zaczyna się rozwiązywać poprzez pisanie procedur po stronie bazy danych. W efekcie część logiki jest realizowana w bazie danych, część po stronie aplikacji, a część na poziomie procesowym. Oczywiście utrzymanie takiego potwora jest kosztowne i trudne, ale za hajs właściciela/inwestora baluj.

Po trzecie pojawiają się problemy ze składowaniem nietypowych, nierelacyjnych, struktur danych. JSON czy XML zapisany w bazie danych? Why not! Struktury typu słowniki z referencjami i hierarchią jako osobna instancja bazy danych? Dawaj Janusz, będzie dobrze! Co ciekawe na końcu jak całość działa w żółwim tempie, to opierdala się programistów.

Znacznie ciekawiej robi się, gdy zaczynamy wchodzić w środowiska rozproszone. Jak zapewnić transakcyjność w momencie, gdy musimy podzielić nasz system na kilka aplikacji, albo musimy całość rozrzucić w klastrze? Jeszcze zabawniejsze są jednak miny naszych mistrzów projektowania z kwadratów i beczek, gdy mówimy im, że jednak te mikroserwisy, to nie ruszą z jednym oraclem i trzeba zapewnić kilkanaście instancji. Wtedy okazuje się, że hajs i architektura mają pewne powiązania.

A można inaczej…

DataLayer

Naszą przygodę z dodawaniem bazy danych do systemu rozpoczniemy od dodania małej zależności do naszego pom-a w ` customer-storage/implementation`:

Listing 1. Airomem na ratunek


<dependencies>
    <dependency>
        <groupid>pl.setblack</groupid>
        <artifactid>airomem-core</artifactid>
        <version>1.1.1</version>
    </dependency>
</dependencies>

Na początek wystarczy, że dane będą przechowywane w pamięci. Serio, serio. RAM jest tani, chyba że masz serwery IBMa, to wtedy jest drogi no ale… Następnie należy, to wszystko jakoś pokleić w kodzie.

Generowanie DAO

Plugin JLNS pozwala na wygenerowanie klas reprezentujących DAO, domyślna zawartość interfejsu jest pusta, a implementacja jest oznaczona adnotacją @Repository(value = „COŚTAMDAO”). Po dorzuceniu kilku rzeczy interfejs CustomerDAO wygląda w nastepujący sposób:

Listing 2. Interfejs CustomerDAO

public interface CustomerDAO {

	Option<Customer> getById(UUID customerId);

	Set<Customer> all();

	UUID add(Customer newCustomer);
}

Zwróćcie uwagę na klasę Customer, która będzie reprezentować encję w rozumieniu DDD. Tu będą składowane wszystkie informacje o użytkowniku, które przechowujemy na poziomie jego składowania. W Warstwie usług biznesowych też pojawi się taka klasa, ale będzie miała inne znaczenie.

Następnie musimy dodać prostą implementację, która jest też ładnym przykładem do pewnego wpisu.

Listing 3. Klasa CustomerDAOInMemory

class CustomerDAOInMemory implements CustomerDAO, Serializable {

	private Map<UUID, Customer> storage = new ConcurrentHashMap();

	@Override
	public Option<Customer> getById(UUID customerId) {
		return Option.of(storage.get(customerId));
	}

	@Override
	public Set<Customer> all() {
		return storage.values()
				.stream()
				.collect(HashSet.collector());
	}

	@Override
	public UUID add(Customer newCustomer) {
		storage.put(newCustomer.getCustomerId(), newCustomer);
		return newCustomer.getCustomerId();
	}
}

Na koniec całość owiniemy w springowy bean, który będzie nam ogarniał logikę związaną z biblioteką airomem:

Listing 4. Klasa CustomerDAOAiromem


@Repository(value = "customerDAO")
public class CustomerDAOAiromem implements CustomerDAO {

	private Persistent<CustomerDaoInMemory> controller;

	@Autowired
	public CustomerDAOAiromem(
			@Qualifier("storagePath") Path path,
			@Qualifier("useRoyalFoodTester") boolean useRoyalFoodTester) {
		Path resolve = Paths.get(path.toAbsolutePath().toString(), "customer");
		this.controller = Persistent.loadOptional(resolve,
				CustomerDAOInMemory::new, useRoyalFoodTester);
	}

	@Override
	public Option<Customer> getById(UUID customerId) {
		return controller.query(c -> c.getById(customerId));
	}

	@Override
	public Set<Customer> all() {
		return controller.query(CustomerDAOInMemory::all);
	}

	@Override
	public UUID add(Customer newCustomer) {
		return controller.executeAndQuery(c -> c.add(newCustomer));
	}

	@PreDestroy
	void close() {
		controller.close();
	}
}

Jak to uruchomimy, to okaże się, że chyba zgubiliśmy gdzieś naszą bazę danych…

Jak JLNS liczy classpath

JLNS w trakcie uruchamiania usługi podaje classpath. Należy do niego m.in. katalog ./application/customer-storage/, ale korzeniem jest katalog ./start. Trochę to nieintuicyjne, ale jak już o tym się pamięta, to można sobie policzyć classpath. I tak oto możemy zapisywać użytkowników w bazie danych. Jeżeli chcemy zmienić bazę na przykład oracla, to wystarczy podmienić implementację CustomerDAO. W przypadku ORMów trzeba by dodać jeszcze mapowania, ale po pierwsze, jeżeli chcemy to zrobić bez zmian w klasie Customer, to możemy wykorzystać XMLa, albo stworzyć klasę CustomerEntity, która będzie naszym opisem struktury bazodanowej.

Na dziś starczy.