Podział aplikacji na mikroserwisy

0

Na wstępie chciałbym zaznaczyć, wiem że do użycia mikoserwisów powinienem mieć dobry powód i że bardzo prawdopodobne jest, że calkowicie nie potrzebuje ich używać. Niemniej jednak, w celach edukacyjnych chciałbym po godzinach napisać aplikację opartą o mikoserwisy.

No i mam w związku z tym kilka pytań. Powiedzmy, że jest to jakaś aplikacja typu to-do list, w przypadku standardowego podejścia, miałbym jedną bazę danych a w niej encje powiązane mniej więcej w ten sposób (zapis w postaci Javowych klas, ale myślę że każdy rozumie o co chodzi)

@Entity
public class User {    
    @Id
    private Long id;    
    private String username;
    @OneToMany
    private List<Task> tasks;
}

@Entity
public class Task {    
    @Id
    private Long id;
    private String description;
    @ManyToOne
    private User assignedTo;
}

Rozumiem, że w przypadku architektury opartej o mikroserwisy, zarówno mój UserService i TaskService będzie korzystać z innych baz danych. Relacje więc nie mają tutaj sensu, powyższe encje wyglądają więc tak:

@Entity
public class User {
    @Id
    private Long id;
    private String username;
    private List<String> taskIds;
}

@Entity
public class Task {
    @Id
    private Long id;
    private String description;
    private String assignedUserId;
}

Czy moze, powstaje trzeci mikorserwis - UserTaskService, a struktura danych prezentuje się wtedy w ten sposób

@Entity
public class User {
    @Id
    private Long id;
    private String username;
}

@Entity
public class Task {
    @Id
    private Long id;
    private String description;
}

@Entity
public class UserTask {
    @Id
    private Long id;
    private Long userId;
    private Long taskId;
}

Niezależnie od tego która opcja jest poprawna, powstaje pytanie jak wygląda flow tworzenia Taska. W pierwszym podejściu serwisy Task i User są dość mocno powiązane, np. TaskService musi wywołać UserService by zaktualizować listę tasków Usera. Dodatkowo, tych parametrów Usera na pewno będzie więcej, więc zaraz się okaze że prawie każdy serwis jest powiązany z Userem. Drugie podejście wydaje się więc nieco bardziej na miejscu, jednak już dla prostego case mamy 3 różne mikroserwisy. Trzecia opcja o jakiej myślałem to MQ, czyli zarówno TaskService i UserService nasłuchują na CreateTaskMessage, powodować to może jednak niespójność danych, w jednym serwisie Task może już być przypisany do Usera, a w drugim nie. No i co jeżeli jakaś potencjalna walidacja w jednym z tych serwisów przejdzie, a w drugim nie?

No i drugie pytanie - DTOsy. Czy to na frontendzie, czy to w innych serwisach, w pewnym momencie trzeba będzie przekazać konkretne dane, a nie tylko id, z jednego serwisu do drugiego. Jak to rozwiązać w świecie mikroserwisów? Generować model na podstawie Swaggera? Jeżeli całość tworzona jest pod jednym parentem Mavenowym, to można wrzucić dto w shared module, ale to chyba trochę przeczy całej idei?

5

Poczytaj o agregatach i bounded contekstach, bo to Ci pomoże przy wytyczeniu granic usług. Jeśli jakaś walidacja wymaga zachowania spójności danych, to powinieneś mieć do nich atomowy dostęp w ramach jeden usługi. Nie dzieli się na usługi ze względu na tabele w bazie, tylko konteksty właśnie, ponieważ dane można - naprawdę! - duplikować. Odnośnie drugiego pytania, codebase usług powinien być niezależny, abyś mógł je wdrażać niezależnie. Jeśli wdrożenie usługi A wymaga jakichś prac w usłudze B, to masz zły podział usług.

Poczytaj Sama Newmana „Building microservices” i Neala Forda „Evolutionary architecture”. A i jeszcze znajdź sobie na necie „DDD quickly”, żeby złapać koncept kontekstów.

0

Oczywiście przez noc nie przeczytałem pozycji o których wspomniałeś, ale staram się pojąć ogólny koncept i wychodzi mi coś takiego:

  1. Zostaje przy 2 serwisach, TaskService i UserService.
  2. UserService odpowiedzialny jest za tworzenie / modyfikacje / pobieranie ogólnych danych o użytkownikach (imię, nazwisko, email), ale nie ma on informacji o szczegółach jak np. lista przypisanych tasków.
  3. TaskService zarządza wszelkimi taskami i trzyma niezbędne informację o przypisanych do tasków użytkownikach, czyli ma tak naprawdę tylko userId.
  4. Przy wyświetlaniu danych taska na UI, wykonuje 2 zapytania HTTP, jedno do GET/task/{taskId} by pobrać dane taska, w tym userId, a potem drugie GET/user/{userId} by pobrać dane użytkownika jak imię, nazwisko

Można by uniknać 2 zapytań HTTP, jakby powielić podstawowe dane użytkownika jak imię i nazwisko w modelu TaskService. Wtedy GET/task/{taskId} zwraca komplet danych.
Wprowadza to jednak zależność między tymi serwisami, są one osobno deployowane i mają osobny codebase, aczkolwiek by działać poprawnie obydwa muszą być online, bo przy przypisaniu taska do użytkownika, muszę zapytać UserService o dane tego użytkownika. Jednak chyba nie ma sensu próbować usunać takich zależności? Powiązanie pomiędzy UserService będzie się pojawiać w wielu innych przypadkach, jak choćby walidacja czy user o podanym userId w ogóle istnieje, teoretycznie każdy kolejny serwis który trzyma userId będzie musiał o to zapytać.

3

Jeśli chodzi o frontend to masz wzorce API gateway i Backend-For-Frontend. Jeśli chcesz wyświetlać dane zagregowane z wielu usług, to albo stawiasz osobną usługę typu agregator, albo zapylasz sobie model tylko do odczytu, który już posiada wszystkie dane i wykonujesz do niego tylko jedno zapytanie. W praktyce robi się mix tych podjeść. Nie projektuj usług backendowych pod odczyt.

1 użytkowników online, w tym zalogowanych: 0, gości: 1