Home Pokégraph - Evolving querying with GraphQL - 1/3
Post
Cancel

Pokégraph - Evolving querying with GraphQL - 1/3


Here we go again with another series. Pokégraph is a small project where I built a catalog of pocket monsters (aka Pokémon) using domain-driven design principles, querying and changing data with GraphQL, and finally building a SPA with Vue 3 and Typescript.

Note: this first post of the series will focus on domain modeling, so if you’re only interested in checking the implementation of GraphQL or the SPA, you can skip to the upcoming posts when they’re published.

Important: Pokémon is © 1995-present Nintendo, The Pokémon Company, Game Freak, Creatures Inc.
This project does not claim to own any characters, concepts or artwork.


Fullstack tech used


Project structure

1
2
3
4
5
├── Pokegraph.Api
├── Pokegraph.Domain
├── Pokegraph.Infrastructure
├── Pokegraph.Tests
└── pokegraph.spa
  • Pokegraph.Api: hosts the GraphQLServer and implements viewmodels, services, queries, mutations and subscriptions.
  • Pokegraph.Domain: aggregates and services.
  • Pokegraph.Infrastructure: implements persistence and hosts a light SQLite database.
  • Pokegraph.Tests: implements unit tests.
  • pokegraph.spa: SPA based built with Vue 3.


My humble take on Domain-Driven Design


First and foremost, this project isn’t meant to be a DDD showcase, but I’ll cover the aspects I used in the actual implementation according to my understanding of the matter. For more academic and detailed information, there are tons of excellent articles, courses, and books about DDD, and I recommend reading the excellent book Domain-Driven Design: Tackling Complexity in the Heart of Software, published in 2003 by Eric Evans.

Some may argue that using DDD on small projects that only perform CRUD like this is overkill. I believe any complex project has started simple and evolved as needed. The DDD approach comes with conventional techniques for designing models and events, promoting a natural separation of concerns. I have a more complex project I named Ecommerce DDD, which I implement a fictional online store. Check it out!.

Hands-on modeling


Pokémon is a worldwide phenomenon that has become part of pop culture in many places around the globe because of the popularity of the games released in the late ’90s, and it’s still happening. I remember being a child and playing those games where I could remember the names of the (at that time) 151 monsters and their evolutions. What good memories!

In both games and anime, a Pokédex is a fictional electronic device designed to catalog and give information about Pokémon. The Pokégraph’s UI aims to resemble one.

With that being said, I’ll play the role of domain expert for this model and use the ubiquitous language to describe it through the code.

Designing Aggregates and validating Domain Invariants

Starting from within this tiny bounded context, I defined the Pokemon aggregate as having:

The Pokémon class was defined as the root because it can exist by itself and act as the top of the cluster composed of the underlying models. It is also an entity, meaning it can be identified by the PokemonNumber. This number is also used for self-referencing, in case a Pokémon has a reference to another one like in a linked-list. The EvolvesToNumber field makes this relation when available.

Value Objects

Further up partitioning the class into smaller objects, I designed some of them as Value Objects. These objects don’t have an identity, and their equality is compared by their values only. They’re immutable objects that are always created when instantiating or updating the Pokemon entity. Value Objects are a handy tool to group things in a meaningful and reusable way, and helps to avoid primitive obsession.

  • PokemonType: defines the type of a Pokémon. A Pokémon can have two, and the primary type is required. When adding a new Pokemon, it validates the type by checking in a dictionary of supported types.
  • BaseStats: underlying values that help determine the growth of a Pokémon’s six major HP, Attack, Defense, Special Attack, Special Defense, and Speed. They start at zero and there’s a validation ensuring it.
  • GenderRatio: it shows the ratio of male and female of the species. With the validation, an automatic calculation is made in case the user inputs only one of the 2 values.
  • PhysicalAttributes: physical characteristics of the pokemon like weight and height. It’s validated to allow only values superior to 0.

Domain Services

When cataloging a new Pokémon, we want to ensure that we can also set up its evolution with an existing valid Pokémon. To make sense of it, it has to refer to a Pokémon that already doesn’t have a direct pre-evolution setup. Otherwise, it would break the evolution chain and allow many Pokémon to evolve to one. Of course, it’s a domain matter, but how, to perform that check?

Well, domain services carry knowledge that doesn’t naturally fit entities and value objects. We need a domain service to check other aggregates by going outside the domain boundaries to make a business decision. Here is an important detail: domain services are always defined by the domain through contracts and using ubiquitous language, but their implementation happens in the proper place depending on their nature. Sometimes the implementation is not pure enough to reside in the domain boundaries. It really can vary from case to case.

Commonly, DDD projects implement “non-pure” domain services like this in the application layer. For this simple project, I put the implementation of the <a href=”https://github.com/

Persistence

Persisting data means we want it to be stored, obviously, but how it’s done depends on many things and technical decisions. We could store our Pokémon in a text file or xml, in a local database, or even call an external endpoint to store it somewhere in the cloud.. none of this matters to our domain. The domain only knows what will be done, not how.

The domain must is always persistent and ignorant, which means it’s never aware of infrastructure matters. An excellent example of it is the repository interfaces, defined in the domain but implemented in the Infrastructure layer, which we will see further down.

Repositories act like an in-memory domain object collection for our Pokémon, and that’s why I always set their names in a plural fashion. However, the plural of Pokémon is officially Pokémon, so I didn’t have better choices here.


Now going to the infrastructure implementation details, I’ve decided to use Entity Framework Core 7.0.10 to map and perform over a SQLite database. You’ll find IPokemon implemented as PokemonRepository.

Last but not least, the infrastructure also holds EF Core assets like the DbContext, configured in this project to ensure the database is created when fetching data for the first time and, applying the existing configurations from this assembly.

You can notice how I split some of the value objects into separated tables to avoid bloating the Pokemon table with all those fields. It can cost a bit more performance due to some LEFT JOIN, but that’s a decision you will take on your own or with your team. EF Core has a very nice support to value objects through owned entities.


One important detail when mapping complex types such value objects is ensuring proper value conversion:

 builder.Property(x => x.Number)
  .HasConversion(
    v => v.Value,
    v => PokemonNumber.From(v))
    .HasColumnOrder(0);

There’s a database file Pokegraph.db with all Pokémon of the 1º generation, and the application is already pointing to it, so you can just play with it with no worries.


Final thoughts


Do you see how much thinking and knowledge are needed to ensure the domain is always valid? Ultimately, everything in the code must match what the domain is meant to model. Therefore, all the business logic is part of our code in a very high-level way.

Also, try to spread the word promoting good design approaches as DDD, combined with SOLID principles for making projects always capable of evolving like a Pokémon!

I hope you have liked it so far. In the next article, I will be focusing on implementing the API with GraphQL.

See you there!


Check the project on GitHub



This post is licensed under CC BY 4.0 by the author.