Mutacje w Netflix DGS są bardzo przyjemne do realizacji. W końcu nie mogło być inaczej, bo zarówno Spring Boot, jak i biblioteki ze stosu OSS Netflixa przyzwyczaiły nas do fajnego API.
Zobaczmy, jak to będzie wyglądało w kodzie.
Mutacje GraphQL
Bardzo dużo czasu poświęca się w przypadku GraphQL na omawianie zapytań do serwera. To dosyć naturalne, bo zwykle w kontekście np. frontów myślimy o ekranach i danych, jakie będziemy na nich wyświetlać. A je trzeba przecież skądś pobrać. Jednak… dane też trzeba zwykle modyfikować.
Netflix DGS oczywiście wspiera mutacje danych. I zapewnia poprawną ich interpretację z poziomu obsługi Spring Boot.
Z punktu widzenia GraphQL typ Mutation, jest drugim typem specjalnym w ramach schema. Wcześniej pisałem o typie Query. W ramach Mutation definiujemy wszystkie mutacje danych jakie będą możliwe w naszym API. Jest to drugi entry point do naszej usługi.
Rozszerzymy teraz schema naszej usługi o trochę więcej danych. Przede wszystkim pozwolimy na dodawanie komentarzy do poszczególnych projektów. Do tego przyda nam się wprowadzenie identyfikatora projektu oraz list komentarzy. Najprościej, jak się da.
type Query {
projects(name: String): [Project]
}
type Mutation {
addComment(projectId: ID!, comment: String!): Boolean
}
type Project {
id: ID!
name: String
description: String
comments: [String]
}
W schema powyżej pojawiło się pole addComment w ramach naszego entry point Mutation. Zakładamy, że komentarz będzie dodany do konkretnego projektu i nie będzie on nullem. Na to wskazuje wykrzyknik po typie argumentu.
Mutacje w Netflix DGS od strony kodu
Przede wszystkim klasa naszego projektu nieco się zmieniła. Jej aktualna implementacja wygląda następująco:
public class Project {
private String id;
private String name;
private String description;
private List<String> comments = new ArrayList<>();
public Project(String id, String name, String description) {
this.id = id;
this.name = name;
this.description = description;
}
public String id() { return id; }
public String name() {
return name;
}
public String description() {
return description;
}
public void addComment(String comment) {
comments.add(comment);
}
}
Widzimy, że jej struktura przypomina tę z schema GraphQL.
Za obslugę mutacji będzie odpowiadać nowy komponent:
@DgsComponent
public class ProjectMutation {
@DgsData(parentType = "Mutation", field = "addComment")
public Boolean projects(DataFetchingEnvironment dfe) {
String projectId = dfe.getArgument("projectId");
String comment = dfe.getArgument("comment");
Optional<Project> project = DATA.PROJECTS.stream()
.filter(p -> Objects.equals(p.id(), projectId))
.findFirst();
project.ifPresent(p -> p.addComment(comment));
return project.isPresent();
}
}
Tutaj na uwagę zasługuje argument DataFetchingEnvironment. Jest to element, który pozwala nam dostać się do informacji zawartej w argumentach mutacji.
Korzystanie z DataFetchingEnvironment nie jest jednak zbyt wygodne. Nie lubimy podatnej na błędu obsługi ręcznej wiązania argumentów projectId i comment. Spring również przyzwyczaił nas do automatycznego wiązania danych. O tym też pomyślał zespół pracujący nad Netflix DGS i w przypadku mutacji możemy skorzystać również z adnotacji @InputArgument. Dzięki temu nasza metoda obsługująca może wyglądać następująco:
@DgsComponent
public class ProjectMutation {
@DgsData(parentType = "Mutation", field = "addComment")
public Boolean projects(@InputArgument("projectId") String projectId, @InputArgument("comment") String comment) {
Optional<Project> project = DATA.PROJECTS.stream()
.filter(p -> Objects.equals(p.id(), projectId))
.findFirst();
project.ifPresent(p -> p.addComment(comment));
return project.isPresent();
}
}
Od strony użytkownika
Do naszych testów ponownie wykorzystamy narzędzie dostępne pod http://localhost:8080/graphiql. Na początku nasze zapytanie:
{
projects {
id,
name,
comments
}
}
zwróci listę projektów bez komentarzy:
{
"data": {
"projects": [
{
"id": "1",
"name": "GraphQL",
"comments": []
},
{
"id": "2",
"name": "Netflix DGS",
"comments": []
},
{
"id": "3",
"name": "Spring Boot",
"comments": []
},
{
"id": "4",
"name": "Spring Framework",
"comments": []
}
]
}
}
Wykołanie mutacji danych będzie wyglądało następująco:
mutation {
addComment(projectId: 2, comment: "Awesome project")
}
W odpowiedzi dostajemy wynik operacji. Zgodnie z naszą implementacją jest to po prostu boolean mówiący o tym, czy się udało dodać komentarz, czy też nie:
{
"data": {
"addComment": true
}
}
Możemy dodać dalej kolejny komentarz i odpytać usługę o aktualne dane:
{
projects {
id
name
comments
}
}
W odpowiedzi widzimy, że nasz projekt o identyfikatorze 2 został już skomentowany dwukrotnie:
{
"data": {
"projects": [
{
"id": "1",
"name": "GraphQL",
"comments": []
},
{
"id": "2",
"name": "Netflix DGS",
"comments": [
"Awesome project",
"Really awesome library!"
]
},
{
"id": "3",
"name": "Spring Boot",
"comments": []
},
{
"id": "4",
"name": "Spring Framework",
"comments": []
}
]
}
}
Podsumowanie
Netflix DGS i mutacje idą w parze. Bardzo fajnie ułatwiają pracę z GraphQL w Spring Boot.
Teoretycznie mogliśmy te i inne operacji realizować wcześniej, przy okazji bardziej niskopoziomowych bibliotek. Jednak dobrze przygotowana nakładka na te biblioteki, czyli DGS, sprawia, że kodowanie jest po prostu przyjemniejsze.
Daj znać, czy planujesz skorzystać z Netflix DGS w swoim następnym projekcie?
HINT dla poszukujących pierwszej pracy. Hey, pamiętacie o tym, jak pisałem parę razy (np. tutaj ✔️ Perfekcyjna rozmowa techniczna Junior Programista i tutaj Rekrutacja Junior Programista – podsumowanie), że projektu na Github mogą być Waszym silnym argumentem? Z moich obserwacji wynika, że prawie 100% takich projektów jest w Spring Boot. Netflix DGS, mechanizm zapytań i mutacje, to chyba niezły element, którym można się wyróżnić – zgodzisz się ze mną?
Fajny ten Netflix DGS. Tutaj znajdziesz więcej informacji o nim: