This is a GraphQL API, made with Java 21 and Spring Boot 3, that has as its goal sync data from football-data.org and expose required data.
- Explaining The Architecture
- How To Run The Project
- Explaining The Mutations and Queries
- Conclusion And Final Considerations
This project was made using a Hexagonal type architecture. What this means is that the project is separated by modules and each module deals with a different concern that's unique to them, as well as their access level.
When it comes to dependencies and plugins, the Hexagonal architecture is extremely useful, given that you can inject dependencies, plugins and other needed tools only in the modules that actually need them.
- The Application layer concerns everything that is going to be consumed (and visible) to the user - from controllers and models to error messages and docs (as well as configurations needed);
- The Core layer concerns business rules, validations and exceptions, being the connection between the user interactions and the data to be consumed;
- The Datasource layer concerns quite literally, the data that will be consumed - be it from a database or any external connection (in this particular case, the football-data.org imports).
Visual representation of the Hexagonal Architecture
This is a Java 21 and Spring Boot 3 based project, that uses Maven as it's build automation tool, and has a dockerized container to run the database,
That being said, in order to successfully run this application you'll need:
- Java 21
- Maven
- Docker
All you need to do to have the container successfully deployed is enter the following commands on a terminal of your choice:
cd docker
docker-compose up -d
If you have a IDE with a configured Java/Maven workspace, all you need to do is make sure your Run/Debug configuration classpath is pointing to the Application module.
In this example, I'm using the Intellij IDEA IDE.
If you don't have an IDE configured, you can use the following commands in your terminal:
mvn clean
mvn install
cd application/
mvn spring-boot:run
note: Make sure you navigate to the application module before running spring boot.
If you followed the steps above correctly, you'll be able to access the GraphQL playground locally through http://localhost:8080/v1/playground ๐
Receives: leagueCode String as argument.
note: you can use the availableLeaguesForSync query to see a list of available League codes that can be used
Sample request
mutation ImportLeague {
importLeague(leagueCode: "WC") {
id
code
name
}
}
Returns: Competition object with the complete tree of content that has been imported to the database.
Sample response
{
"data": {
"importLeague": {
"code": "WC",
"id": "2000",
"name": "FIFA World Cup"
}
}
}
Sample request
query AvailableLeagues {
availableLeaguesForSync {
code
name
}
}
Returns a list of available Leagues that can be imported using the importLeague Mutation
Sample response
{
"data": {
"availableLeaguesForSync": [
{
"code": "WC",
"name": "FIFA World Cup"
},
{
"code": "CL",
"name": "UEFA Champions League"
}
]
}
}
Receives: leagueCode String as argument and teamName (optional).
note: The league provided has to be already synced, otherwise it will not return
the expected result.
Sample request
query FindPlayers {
findPlayers(leagueCode: "CL", teamName: "Arsenal") {
id
person {
dateOfBirth
gamePosition
id
name
nationality
}
}
}
Returns: A List of players.
{
"data": {
"findPlayers": [
{
"id": "4832",
"person": {
"dateOfBirth": "1995-09-15",
"gamePosition": "Goalkeeper",
"id": "4707",
"name": "David Raya",
"nationality": "Spain"
}
},
{
"id": "5530",
"person": {
"dateOfBirth": "1998-05-14",
"gamePosition": "Goalkeeper",
"id": "4890",
"name": "Aaron Ramsdale",
"nationality": "England"
}
}
// so it goes...
]
}
}
Receives: teamName String as argument and sortPlayers (optional)
Sample request
query FindTeamInformation {
findTeam(teamName: "Brazil", sortPlayers: true) {
id
name
shortName
tla
address
area {
code
flag
id
name
}
coach {
id
person {
name
dateOfBirth
nationality
}
}
players {
id
person {
name
nationality
dateOfBirth
gamePosition
}
}
}
}
Returns: A Team object with a list of players, sorted or unsorted depending on the sortPlayers argument.
{
"data": {
"findTeam": {
"id": "764",
"name": "Brazil",
"shortName": "Brazil",
"tla": "BRA",
"address": "Rua Victor Civita 66 Bloco 1, Edifico 5 Rio de Janeiro, RJ 22775-044",
"area": {
"code": "BRA",
"flag": "https://crests.football-data.org/764.svg",
"id": "2032",
"name": "Brazil"
},
"coach": {
"id": "11165",
"person": {
"name": "Fernando Diniz",
"dateOfBirth": "1974-03-27",
"nationality": "Brazil"
}
},
"players": [
{
"id": "2028",
"person": {
"name": "Alex Sandro",
"nationality": "Brazil",
"dateOfBirth": "1991-01-26",
"gamePosition": "Defence"
}
},
{
"id": "15904",
"person": {
"name": "Alex Telles",
"nationality": "Brazil",
"dateOfBirth": "1992-12-15",
"gamePosition": "Defence"
}
},
{
"id": "1795",
"person": {
"name": "Alisson",
"nationality": "Brazil",
"dateOfBirth": "1992-10-02",
"gamePosition": "Goalkeeper"
}
},
{
"id": "97085",
"person": {
"name": "Antony",
"nationality": "Brazil",
"dateOfBirth": "2000-02-24",
"gamePosition": "Offence"
}
}
// so it goes...
]
}
}
}
An alternative way of searching for a player, using instead the name of the player as Argument, and returning a list of players as the result.
This can be used as an auto-complete, given a like sql query was used.
Receives: playerName String as Argument.
Sample request
query SearchPlayer {
searchPlayer(playerName: "An") {
id
person {
name
dateOfBirth
gamePosition
nationality
}
}
}
Returns: A list of players (same as in findPlayers)
An alternative way of searching for a team, using instead the name of the team as Argument, and returning a list of team as the result.
This can be used as an auto-complete, given a like sql query was used.
Receives: teamName String as Argument.
Sample request
query SearchTeam {
searchTeam(teamName: "Ar") {
id
name
shortName
tla
address
}
}
Returns: A list of teams (same object as in findTeam - but listed)
I sure hope you can see that I'm very passionate about the work I do and I hope it shows in my code ๐
This project was started on 2023-12-15, and given it's short time I wasn't able to finish the unit tests, other than that, I'm very proud of it ๐
If you have any questions, feel free to reach out on any of my socials:
This and other Projects are available on my github page!