W ostatnich dwóch częściach mini-serii o architekturze opisałem czym są obiekty domenowe, dto oraz jak wygląda architektura trójwarstwowa/wielowarstwowa.
Warto pokazać kod takiej aplikacji. Użyjemy do tego frameworka Spring, który jest najpopularniejszym frameworkiem w świecie Javy.
W tym wpisie używamy Spring Framework. Jego fundamenty możesz poznać w tym darmowym kursie.
Zacznijmy od utworzenia projektu na https://start.spring.io
Potrzebujemy trzech dependencji – Spring Data JPA (persystencja), H2( (lokalna baza danych), Spring Web (warstwa „prezentacji”)

Warstwa logiki biznesowej
Zaczniemy od funkcjonalności naszego programu, bo w końcu ta jego część zarabia. Nie będziemy się silić w tym przykładzie na oryginalność i wykorzystamy obiekty domenowe z kursu o fundamentach JPA – Reservation i Guest, z ustawionymi adnotacjami persystencji. Nasz program ma służyć do tworzenia rezerwacji hotelowych, więc te dwa obiekty są naszymi głównymi graczami.
package pl.clockworkjava.threetier.guest;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Guest {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
private int age;
private String personalIdentificationNumber;
public Guest() {
}
public Guest(String name, int age, String personalId) {
this.name = name;
this.age = age;
this.personalIdentificationNumber = personalId;
}
public long getId() {
return this.id;
}
public void setAge(int newAge) {
this.age = newAge;
}
public int getAge() {
return this.age;
}
public String getName() {
return name;
}
public String getPersonalIdentificationNumber() {
return personalIdentificationNumber;
}
}
package pl.clockworkjava.threetier.reservation;
import pl.clockworkjava.threetier.guest.Guest;
import javax.persistence.*;
import java.util.List;
@Entity
public class Reservation {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
@OneToMany
private List<Guest> guests;
public Reservation(List<Guest> guest) {
this.guests = guest;
}
}
Poza obiektami domenowymi w warstwie logiki biznesowej potrzebujemy jeszcze serwisów. Są to obiekty, które udostępniają operację w danej domenie biznesowej. Podstawowym z nich będzie pobierz wszystkich gości. Teraz w zależności od tego jak zadecydowano w projekcie/zespół zadecyduje, albo zwracamy obiekty domenowe Guest i wychodzą one „poza domenę”, albo już na tym etapie tworzymy DTO i je wystawiamy na świat. Ja wybiorę pierwszą opcję.
package pl.clockworkjava.threetier.guest;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.List;
@Service
public class GuestService {
public List<Guest> getAllGuests() {
return Collections.emptyList();
}
}
Warto zwrócić uwagę na dwie rzeczy. Po pierwsze adnotacja @Service. Jest to oznaczenie równoważne w funkcjonalności adnotacji @Component, jednak dzięki użyciu @Service osoba czytająca kod od razu wie z jakim typem elementu programu ma do czynienia.
Po drugie zwracamy pustą listę… dlaczego?
Ano dlatego, że za operację z radziny CRUD odpowiada warstwa persystencji, a konkretnie element Repozytorium (dawniej zwany DAO)
Warstwa utrwalania danych
na tej warstwie żyją repozytoria, czyli specjalne obiekty odpowiedzialne za operacje CRUD na danej domenie, oraz za wyszukiwanie po kluczach.
W Springu repozytoria podobnie jak serwisy oznaczamy jako @Repository i również ma to głównie znaczenie dla programisty czytającego kod.
package pl.clockworkjava.threetier.guest;
import org.springframework.stereotype.Repository;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.List;
@Repository
public class GuestRepository {
@PersistenceContext
EntityManager entityManager;
public List<Guest> getAllGuests() {
return entityManager
.createQuery("Select guest from Guest guest", Guest.class)
.getResultList();
}
}
Repozytorium odpowiada więc za wszelkie operacje na Entity Managerze, lub bezpośrednio na bazie danych.
Tak więc skoro w repozytorium zaimplementowaliśmy pobieranie wszystkich gości, to możemy teraz użyć go w serwisie.
package pl.clockworkjava.threetier.guest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class GuestService {
@Autowired
GuestRepository repository;
public List<Guest> getAllGuests() {
return repository.getAllGuests();
}
}
Warstwa prezentacji
Nadszedł czas na finalną warstwę, czyli prezentacji danych. Prezentacja ta może mieć najróżniejszą postać. My wystawimy na świat REST API.
Dla przykładu wystawimy jedną metodę
package pl.clockworkjava.threetier.guest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class GuestController {
@Autowired
GuestService service;
@GetMapping("guest")
public List<Guest> getAllGuests() {
return service.getAllGuests();
}
}
Wszsytko pięknie poza jedną rzeczą – obiekt domenowy Guest posiada wrażliwe dane. W obecnych czasach wszelkie dane osobowe są wrażliwe, ale konkretnie chodzi nam o PESEL.
Możemy więc go wyciąć, najprościej jest zdefiniować DTO, tam dodać tylko niezbędne dane i je wysyłać „w świat”.
package pl.clockworkjava.threetier.guest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class GuestService {
@Autowired
GuestRepository repository;
public List<GuestReadDTO> getAllGuests() {
return repository.getAllGuests()
.stream()
.map(guest -> new GuestReadDTO(guest.getAge(), guest.getName(), guest.getId()))
.collect(Collectors.toList());
}
}
package pl.clockworkjava.threetier.guest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class GuestController {
@Autowired
GuestService service;
@GetMapping("guest")
public List<GuestReadDTO> getAllGuests() {
return service.getAllGuests();
}
}
Po uruchomieniu programu i odwiedzając z przeglądarki adres http://localhost:8080/guest powinniśmy otrzymać w odpowiedzi pustą tablicę. W naszej bazie brakuję jakichkolwiek danych. Stwórzmy więc komponent odpowiedzialny za inicjalizacje bazy danymi.
Po pierwsze utwórzmy metodę odpowiedzialną za tworzenie nowych obiektów domenowych w repozytorium
package pl.clockworkjava.threetier.guest;
import org.springframework.stereotype.Repository;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;
import java.util.List;
@Repository
public class GuestRepository {
@PersistenceContext
EntityManager entityManager;
public List<Guest> getAllGuests() {
return entityManager
.createQuery("Select guest from Guest guest", Guest.class)
.getResultList();
}
@Transactional
public void create(String name, int age, String personalId) {
Guest newOne = new Guest(name, age, personalId);
entityManager.persist(newOne);
}
}
Teraz utwórzmy komponent implementujący interfejs CommandLineInterface. Metoda run jest uruchamiana, gdy tylko kontekst Spring zostanie stworzony.
package pl.clockworkjava.threetier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import pl.clockworkjava.threetier.guest.GuestRepository;
@Component
public class DBInit implements CommandLineRunner {
@Autowired
private GuestRepository guestRepository;
@Override
public void run(String... args) throws Exception {
this.guestRepository.create("Paweł", 22, "1111111");
}
}
Po ponownym uruchomieniu aplikacji i odwiedzeniu adresu localhost:8080/guest powinniśmy dostać informacje o gościu o imieniu Paweł, wieku 22 i id sytemowym 1. Nie ma już informacji o PESEL.
Podsumowanie
Stereotypowe komponenty Springowe (serwis, repozytorium, kontroler) doskonale wpisują się w trójwarstwowy podział aplikacji, który pozwala na odseparowanie od siebie obowiązków poszczególnych warstw. Oczywiście nie jest to dzieło przypadku.

Kod aplikacji znajdziesz w publicznym repozytorium GitHub.
P.S. Jeśli chcesz wiedzieć jak stworzyć aplikację full-stack (ReactJS+Spring Framework) zapraszam do rzucenia okiem na te warsztaty programistyczne (2h kurs)