Obiekt domenowy, DTO, DAO

Pracując z większymi systemami często spotykamy się z pojęciem obiektów domenowych, DTO, a okazyjnie natrafiamy nawet na DAO. Każda z tych nazw określa pewien typ obiektów, które mają określone zadanie.

Domeny i obiekty domenowe

Zacznijmy od najważniejszego z nich, czyli obiektu domenowego. Domena w programowaniu to, ogólnie mówiąc, pewien obszar odpowiedzialności naszego programu.

Tworząc system do rezerwacji pokoi hotelowych mamy na przykład trzy odpowiedzialności takiego systemu:

  1. zarządzanie pokojami
  2. zarządzanie listą gości
  3. zarządzanie samymi rezerwacjami

To są nasze trzy domeny: pokoje, goście i rezerwacje. Każda z tych domen jest odciętym od świata zewnętrznego (poza jedną parą “drzwi” – serwisem dostępowym) małym światem.

Domena zarządzania pokojami będzie odpowiadać za dodawanie nowych pokoi do systemu, oznaczanie, które są wolne, a które zarezerwowane, ustawianiem jaki typ łóżka, ile osób w danym pokoju może się znajdować etc. etc. Do tego oczywiście dochodzą wymagania niebiznesowe, jak persystencja danych, logowanie tego, co się dzieje i tak dalej.

Obiekt domenowy to serce całej domeny. Jest to główna klasa, której to obiekty reprezentują główną rzecz/idee obsługiwaną przez daną domenę.

W naszym wypadku wyznaczenie obiektów domenowych jest dość proste. Będą to klasy odpowiedzialne za przechowywanie danych o pokojach, gościach i rezerwacjach.

Przykład z pewnego momentu mojego kursu Kompletna aplikacja w języku Java – od zera do installera:

package pl.clockworkjava.domain.reservation;

import pl.clockworkjava.domain.guest.Guest;
import pl.clockworkjava.domain.reservation.dto.ReservationDTO;
import pl.clockworkjava.domain.room.Room;

import java.time.LocalDateTime;

public class Reservation {

    private final long id;
    private final Room room;
    private final Guest guest;
    private final LocalDateTime from;
    private final LocalDateTime to;

    public Reservation(long id, Room room, Guest guest, LocalDateTime from, LocalDateTime to) {
        this.id = id;
        this.room = room;
        this.guest = guest;
        this.from = from;
        this.to = to;
    }

    String toCSV() {
        return String.format("%s,%s,%s,%s,%s%s",
                this.id,
                this.room.getId(),
                this.guest.getId(),
                this.from.toString(),
                this.to.toString(),
                System.getProperty("line.separator"));
    }

    public long getId() {
        return this.id;
    }

    public ReservationDTO getAsDTO() {
        return new ReservationDTO(this.id, this.from,
                this.to, this.room.getId(),
                this.room.getNumber(), this.guest.getId(),
                this.guest.getFirstName() + " " + this.guest.getLastName());
    }

    public LocalDateTime getFrom() {
        return this.from;
    }

    public Room getRoom() {
        return this.room;
    }

    public LocalDateTime getTo() {
        return this.to;
    }
}
 

Obiekty domenowe są zapisywane w bazie danych (albo persystowane w inny sposób) i zmiany w ich wartościach są audytowane (logowane, zapisywane by było wiadomo kiedy i co się zmieniło). Generalnie stanowią serce naszego programu.

DTO (Data Transfer Object)

Drugim typem obiektów jest DTO, czyli Data Transfer Object. Są to obiekty, które są tylko zbiorem danych (pól) i posiadają tylko zestaw getterów. Nie posiadają one żadnej logiki biznesowej.

Służą one do przesłania danych poza część aplikacji odpowiedzialną za logikę biznesową do na przykład front-endu. Jeśli ktoś chcę, by jego kod był bardzo “zamknięty” to posuwa się krok dalej i z domeny nie “wychodzą” obiekty domenowe, a okrojone DTO właśnie.

Jeśli nasza aplikacja udostępnia dane o rezerwacjach poprzez interfejs REST to zamiast wysyłać na świat cały obiekt domenowy Reservation, który może posiadać bardzo dużo informacji (np. dane adresowe osoby aktualnie rezerwującej dany pokój) i narażać się na przechwycenie tych danych lepiej stworzyć obiekt DTO. Posiada on część danych niezbędnych w danym kontekście i jest tworzony na podstawie obiektu domenowego. To właśnie DTO jest zwracane przez REST.

Przykład DTO z domeny rezerwacji:

package pl.clockworkjava.domain.reservation.dto;

import java.time.LocalDateTime;

public class ReservationDTO {

    private long id;
    private LocalDateTime from;
    private LocalDateTime to;
    private long roomId;
    private int roomNumber;
    private long guestId;
    private String guestName;

    public ReservationDTO(long id, LocalDateTime from, LocalDateTime to, long roomId,
                          int roomNumber, long guestId, String guestName) {
        this.id = id;
        this.from = from;
        this.to = to;
        this.roomId = roomId;
        this.roomNumber = roomNumber;
        this.guestId = guestId;
        this.guestName = guestName;
    }

    public long getId() {
        return id;
    }

    public LocalDateTime getFrom() {
        return from;
    }

    public LocalDateTime getTo() {
        return to;
    }

    public long getRoomId() {
        return roomId;
    }

    public int getRoomNumber() {
        return roomNumber;
    }

    public long getGuestId() {
        return guestId;
    }

    public String getGuestName() {
        return guestName;
    }
}

Obiekty typu DTO są dość “lekkie” i nie są w żaden sposób przechowywane w bazie danych oraz audytowane, więc możemy klas takich obiektów tworzyć wiele. Obiekt domenowy Reservation może mieć kilka DTO – każdy tworzony na potrzebny jednej metody, jednego “endpointa” w systemie.

Również DTO nie muszą posiadać danych tylko z jednego obiektu domenowego. Możemy tworzyć DTO, który łączy dane o rezerwacji (daty), pokoju (numer) oraz gościu (imię i nazwisko) i taki zestaw danych udostępniać.

Wszystko wedle wymagań, bo ponownie – DTO są “tanie”.

DAO (Data Access Object)

O ile obiekty domenowe czy DTO to “tradycyjne” obiekty, których w systemie istnieje wiele to DAO w danej domenie jest singletonem. Jest to klasa odpowiedzialna za zapisywanie i odczyt obiektów domenowy do bazy danych czy innego miejsca, gdzie zapisujemy trwale dane z obiektów domenowych. Obecnie o wiele częściej zamiast nazwy DAO funkcjonuje nazwa Repozytorium (z nomenklatury Domain Driven Design). Technicznie repozytoria, poza samą translacją z i do bazy danych, mają jeszcze dodatkowe odpowiedzialności jednak można spokojnie uznać DAO i Repozytoria pełnią te same funkcje w architekturze.

Przykładowy wycinek kodu Repozytorium:

package pl.clockworkjava.domain.reservation;

//...

public class ReservationDatabaseRepository implements ReservationRepository {

    private List<Reservation> reservations = new ArrayList<>();
//...

    @Override
    public Reservation createNewReservation(Room room, Guest guest, LocalDateTime from, LocalDateTime to) {

        try {
            String fromAsStr = from.format(DateTimeFormatter.ISO_DATE_TIME); // yyyy-MM-dd HH:mm:ss
            String toAsStr = to.format(DateTimeFormatter.ISO_DATE_TIME);
            
            String createTemplate = "INSERT INTO RESERVATIONS(ROOM_ID, GUEST_ID, RESERVATION_FROM, RESERVATION_TO) VALUES (:roomId, :guestId, :resFrom, :resTo)";
            PreparedStatement statement = SystemUtils.connection.prepareStatement(createTemplate, Statement.RETURN_GENERATED_KEYS);
            statement.setLong("roomId", room.getId());
            statement.setLong("guestId", guest.getId());
            statement.setString("resFrom", fromAsStr);
            statement.setString("resTo", toAsStr);
            statement.executeQuery();
            ResultSet rs = statement.getGeneratedKeys();
            long id=-1;
            while(rs.next()) {
                id = rs.getLong(1);
            }

            Reservation newReservation = new Reservation(id, room, guest, from, to);
            this.reservations.add(newReservation);
            return newReservation;

        } catch (SQLException throwables) {
            System.out.println("Błąd przy tworzeniu rezerwacji");
            throw new RuntimeException(throwables);
        }
    }
//...
}

I na tym zakończymy.

By być na bieżąco i mieć realny wpływ na tematykę tworzonych przeze mnie artykułów zapraszam do dołączenia do mojego newslettera.

3 myśli w temacie “Obiekt domenowy, DTO, DAO”

  1. W ostatnim przykładzie masz “prawie” SQL Injection. Przyzwoitość każe użyć w stylu:

    statement =con.prepareStatement(“SELECT * from employee WHERE userID = :userId”);
    statement.setString(userId, userID);
    ResultSet rs = statement.executeQuery();

    A więc przykazaywania parametrów za pomocą :name lub przez ‘?’.

    Użycie String.format to igranie z ogniem.

    1. Dokładnie tak jest. akurat wziąłem losowy kawałek repozytorium z losowego momentu tworzenia aplikacji, zanim prepared statements zostały wprowadzone. Natomiast uwaga jak najbardziej słuszna i dla przyzwoitości poprawie, bo faktycznie przez nieuwagę moją ktoś może zły przykład brać 🙂

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *