Skip to content

FCMS is a Spring Boot REST API for dealing with the management of Football Clubs. It also serves as a comprehensive guide for building Spring Boot REST APIs.

Notifications You must be signed in to change notification settings

arsy786/football-club-management-system

Repository files navigation

Football-Club-Management-System

FCMS is a Spring Boot REST API for dealing with the management of Football Clubs.

Table of Contents

0. Getting Started
1. Motivation
2. REST API Design
     2.1 ERD
     2.2 Deciding Foreign Keys in a Relationship
3. Spring Boot REST API Development
     3.1 Project Setup
          3.1.1 Initialise Spring Boot Project
          3.1.2 Connect Spring Boot to Database
     3.2 Model / Entity Layer
          3.2.1 DTO
          3.2.2 Mapper
     3.3 Repository Layer
     3.4 Service Layer
          3.4.1 Exception Handling
     3.5 Controller Layer
          3.5.1 Extra CRUD Methods/Endpoints
4. Extra Features to Consider
     4.1 Documentation
     4.2 Logging
     4.3 Testing
     4.4 Security
     4.5 Lombok - JPA

0. Getting Started

Prerequisites

  • Git: For cloning the repository.
  • Java JDK 1.8: Required to compile and run the Java application.
  • Maven: For building the project. The Maven wrapper is included, so a global installation is not necessary.
  • IDE of Your Choice (Optional): Useful for running and debugging the application directly within an IDE (e.g., IntelliJ IDEA, Eclipse, VSCode).
  • Postman (Optional): For making API calls and testing the REST API endpoints.

Running the App

  1. Open your terminal or command prompt.

  2. Clone the repository using Git:

    git clone https://github.com/arsy786/football-club-management-system.git
  3. Navigate to the cloned repository's root directory:

    cd football-club-management-system
  4. Run the following Maven command to build and start the service:

    # For Maven
    mvn spring-boot:run
    
    # For Maven Wrapper (automatically uses the correct Maven version)
    ./mvnw spring-boot:run

    This command compiles the application, starts the Spring Boot application, and deploys it on the embedded server. It's convenient during development for quick testing and debugging.

    The application should now be running on localhost:8080.

Database Configuration

The application supports using a preset H2 database or your own database setup:

  • For H2 Database: Ensure application.properties is set to use the default profile:

    spring.profiles.active=default
  • For Custom Database Setup: Ensure application.properties is set to use the prod profile:

    spring.profiles.active=prod

    Then, update application-prod.properties with your database properties. If using a database other than PostgreSQL, remember to update the pom.xml with the correct dependencies for your chosen database.

Building the App

  1. To build the application for deployment, run the following command:

    # For Maven
    mvn clean package
    
    # For Maven Wrapper
    ./mvnw clean package

    This command compiles the application, runs any tests, and packages the compiled code into a runnable .jar file located in the target directory.

  2. After building the application, you can run it using the following command:

    java -jar target/football-club-management-system-0.0.1-SNAPSHOT.jar

    This starts the Spring Boot application using the packaged .jar file. It's suitable for deploying or sharing the application in a production-like environment.

Using the API

The REST API is documented with Swagger.

After starting the application, access the Swagger UI to interact with the API at:

http://localhost:8080/swagger-ui/index.html

1. Motivation

The purpose of this project is to grow my knowledge of Spring Boot. I plan to gradually implement new features to steadily grow my understanding of the framework, as well as concepts surrounding REST API development.

Spring Boot Roadmap

I came across a 'Spring Boot Roadmap' and used it as a guide to structure my learning. After boring myself with watching many hours of courses/tutorials and reading plenty of articles/forums limited to basic 1 entity REST APIs (that lacked any progression in complexity), I realised that I actually did not know how to apply ANY of the new concepts I had 'learnt'.

Eventually, I decided that the best way to grasp these concepts would be to actually implement the theory & guides into a project that interests me (and is also designed by me)!

Designing my own application means that I can test my understanding and skills from the very ground up. I am a fan of football therefore the idea of building the FCMS REST API came natural to me.

2. REST API Design

2.1 ERD

FCSM ERD

I wanted to design the Entity Relationship Diagram (ERD) so that I could make use of all JPA relationships available.

Description JPA Relationship
A Team has many Players @OneToMany
Many Teams play in a League @ManyToOne
A Team has an Owner @OneToOne
A Team has a Stadium @OneToOne
A Team plays in many Cups AND a Cup has many Teams* @ManyToMany

*Another way of describing this is, a Team can play in many Cups (and many Teams can play in the same Cup)

NOTE: @ManyToMany = @OneToMany + @ManyToOne

2.2 Deciding Foreign Keys in a Relationship

@ManyToOne:

  • FK (and config) in Many (Child) side.
  • If you want a bidirectional relationship, must add List< Child > field and map the corresponding @OneToMany annotation in Parent entity.

Bi-directional relationship example:

public class Child {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long childId;

    @ManyToOne
    @JoinColumn(name = "parent_id", referencedColumnName = "parentId") //fk
    private Parent parent;
}

public class Parent {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long parentId;

    @OneToMany(mappedBy = "parent")
    private List<Child> children;
}

@OneToOne / OneTo(Perhaps)One:

  • FK in either side.
  • FK preferred in Dependent entity (if one exists).
  • Example of independent/dependent entity: a Student is independent of a Student Mentor, the Student Mentor only exists if the Student does. Therefore, Student Mentor is dependent on Student.

Student (Independent) and Student Mentor (Dependent) relationship Example:

public class StudentMentor {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long studentMentorId;

    @OneToOne
    @JoinColumn(name = "student_id", referencedColumnName = "studentId") //fk
    private Student student;
}

public class Student {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long studentId;

    @OneToOne(mappedBy = "student")
    private StudentMentor studentMentor;
}

@ManyToMany:

  • Need intermediate table (Join Table) with FK from both sides, which combine to form a composite key.
  • This can be configured on either side in Java, but it is preferred to do the config in the Dependent entity.
  • Example of a ManyToMany relationship: X can have many Y's AND Y can have many X's.
  • Cup is the dependent entity as Cups only exist if the Teams do.

Team (Independent) and Cup (Dependent) relationship example:

public class Cup {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long cupId;

    @ManyToMany
    @JoinTable(
            name = "team_cup_map",
            joinColumns = @JoinColumn(name = "cup_id", referencedColumnName = "cupId"),
            inverseJoinColumns = @JoinColumn(name = "team_id", referencedColumnName = "teamId")
    ) // both fk's for intermediate table
    private List<Team> teams;
}

public class Team {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long teamId;

    @ManyToMany(mappedBy = "teams")
    private List<Cup> cups;
}

NOTE: FK in Child/Dependent table references its PK in Parent/Independent table.

Q) How to decide if you should have @OneToOne table or merge the attributes into 1 table?
A) If data in one table is related to, BUT does NOT 'belong' to the entity described by the other, then keep separate.

3. Spring Boot REST API

DISCLAIMER: All features (Entity, DTO, Controller, etc.) associated with 'Team' will aim to maintain the highest coding standards; implement the most concepts/features; and contain the most detail in regard to complying with REST API development standards.

Features associated with other Models (Player, Owner, etc.) will only contain what is necessary and convenient for a project of this scale e.g. other Controllers will not contain OpenAPI documentation simply for convenience, code readability and code reusability.

3.1 Project Setup

3.1.1 Initialise Spring Boot Project

3.1.2 Connect Spring Boot to Database

  • Connect easily via Spring Data JPA.
  • Add configuration to applications.properties file to setup DB connections (and other features i.e. port).
  • Example of H2 database connection setup with console enabled and data pre-loaded:
server.port=8080

spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.datasource.platform=h2

spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.defer-datasource-initialization=true
spring.jpa.hibernate.ddl-auto=create-drop

spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
  • Can add initial data by adding file-name.sql in resources folder and adding these lines to application.properties:
spring.sql.init.mode=always
spring.sql.init.data-locations=classpath:file-name.sql
  • file-name.sql will contain SQL statements to populate the entities created by JPA/Hibernate during Spring Boot start-up:
INSERT INTO team (name, city, manager)
VALUES ('Manchester United F.C.', 'Manchester', 'Erik ten Hag');

INSERT INTO team (name, city, manager)
VALUES ('Manchester City F.C.', 'Manchester', 'Pep Guardiola');

NOTE: Adding initial data is useful in a DEV environment for testing purposes, but is not needed in production.

3.2 Model / Entity Layer

  • Create a POJO for your Models/Entities.
  • This class (entity) will be registered with Hibernate as result of implementing JPA Annotations.
  • Can add attributes (name, nullable, unique, etc.) to JPA Annotations (@Table, @Column, etc.) to add constraints to DB.
  • Can use Lombok annotations to reduce boilerplate code.

NOTE: Use the annotations from javax.persistence.* for adding constraints in the Model layer.
NOTE: Be careful of Lombok - JPA integration

JPA Annotations with Lombok example:

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "user")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "name", nullable = false, unique = true)
    private String userName;

    @Column(name = "email", nullable = false, unique = true)
    private String email;

    @Column(name = "bio")
    private String bio;

}

For code implementation example(s) check: Team.java or Cup.java

3.2.1 DTO

  • DTO (data transfer object) is an object that carries data between processes.
  • DTOs for JPA entities generally contain a subset of entity attributes.
  • For example, if you need to expose only a few of the entity attributes via REST API, you can map entities to DTOs with those attributes and serialize only them.
  • Basically, DTOs allow you to decouple presentation/business logic layer from the data access layer.
  • Can use Lombok annotations to reduce boilerplate code.

NOTE: Can use JPA Buddy to generate DTOs (YouTube/JPABuddy)
NOTE: DTOs solve Jackson JSON infinite recursion problem for bidirectional relationships.

User model vs. UserDto example:

@Data
public class User {
    private String id;
    private String name;
    private String password;
    private List<Role> roles;
}

@Data
public class UserDto {
    private String name;
    private List<String> roles;
}
  • Can use DTOs to reference other entities via associations.

OwnerDto containing PetDto id and name example:

@Data
public class OwnerDto implements Serializable {
    private final Long ownerId;
    private final String name;
    private final String country;
    private final List<PetDto> pets;

    @Data
    public static class PetDto implements Serializable {
        private final Long petId;
        private final String name;
    }
}
  • Can use Java Bean Validation annotations on fields (@NonBlank, @Email, etc.) to validate user inputs.
  • Some annotations accept additional attributes, but the "message" attribute is common to all of them. This is the message that will usually be rendered when the value of the respective property fails validation.

NOTE: Use the annotations from javax.validation.constraints.* for adding validation in the DTO layer.

Java Bean Validation example:

public class UserDto {

  @NotNull(message = "Name cannot be null")
  private String name;

  @NotEmpty
  @Size(min = 8, message = "password should have at least 8 characters")
  private String password;

  @Size(min = 10, max = 200, message
          = "About Me must be between 10 and 200 characters")
  private String aboutMe;

  @Min(value = 18, message = "Age should not be less than 18")
  @Max(value = 150, message = "Age should not be greater than 150")
  private int age;

  @Email(message = "Email should be valid")
  private String email;

  // standard setters and getters
}

For code implementation example(s) check: TeamDTO.java or CupDTO.java

3.2.2 Mapper

  • Mapper is a technique to transfer data from DTOs to Entitys or vice versa.
  • MapStruct is a code generator that greatly simplifies the implementation of mappings.
  • When using MapStruct, you only define simple method signatures, converting Entity to DTO, DTO to Entity, List of Entity to List of DTOs.
  • The MapStruct annotation (@Mapper) will generate implementation code for you during build time.

NOTE: Can use JPA Buddy to generate Mappers (YouTube/JPABuddy)
NOTE: If using MapStruct, ensure that you run mvn clean compile/install before starting application.
NOTE: Ensure correct configuration when using MapStruct and Lombok in the same project.

Example of Mapper class with MapStruct @Mapper Annotation:

@Mapper(componentModel = "spring")
public interface UserMapper {

    UserDto userToUserDto (User user);

    List<UserDto> usersToUsersDto(List<User> users);

    User userDtoToUser(UserDto userDto);
}

For code implementation example(s) check: TeamMapper.java or CupMapper.java

3.3 Repository Layer

  • This will interact with the underlying DB.
  • To program CRUD (and JPA) operations on Student entities, need to have a StudentRepository interface.
  • Spring Data provides a layer on top of JPA that offers convenient ways to reduce boiler plate code.
  • CrudRepository interface extends Repository to provide CRUD functionality.
  • JpaRepository interface extends CrudRepository to give us the JPA specific features.
  • For functions that are not already present in JpaRepo, add new methods/queries in the StudentRepository interface.
  • For simple queries, Spring can easily derive what the query should be from just the method name.

Example of a simple query:

public interface BookRepository extends JpaRepository<Book, Long> {

    List<Book> findByName(String name);

}
  • For more complex queries, can annotate a repository method with the @Query annotation where the value contains the JPQL or SQL to execute.

Example of complex queries:

public interface BookRepository extends JpaRepository<Book, Long> {

    @Query("select b from Book b where upper(b.title) like concat('%',upper(:title), '%')")
    List<Book> findByTitle(@Param("title") String title);

}

public interface StudentRepository extends JpaRepository<Student, Long> {

    @Query("SELECT s FROM Student s WHERE s.email = ?1")
    Optional<Student> findStudentByEmail(String email);

}

For code implementation example(s) check: TeamRepository.java or CupRepository.java

3.4 Service Layer

  • The Service is where all the implementation is done, and it interacts with the Repository (DB) and Controller layers.
  • It will only take the data from the Controller layer and transfer it to the Repository layer.
  • It will also take the data from the Repository layer and send it back to the Controller layer.
  • The Service exposes methods that will be called from the Controller.
  • This layer is where all the business logic code is implemented, which consists of basic CRUD methods.

NOTE: DTOs are injected in this layer, as any response being passed to the Controller must be in the form of a DTO.

For code implementation example(s) check: TeamService.java or CupService.java

3.4.1 Exception Handling

For code implementation example(s) check: .../exception/

3.5 Controller Layer

  • Now we can implement a Controller class to define our API URLs and use the Service class to manage data.
  • Different methods are defined in the Controller, these methods will be called by different endpoints.
  • The endpoint methods in the Controller typically match those in its corresponding Service layer.
  • Controller consumes (via endpoint) and responds (via service) with DTO's only.
  • Handles HTTP requests.
  • Can use Java Bean Validation Annotation (@Valid) to enable validation within the Controller layer.
  • The @Valid Annotation ensures the validation of an object passed as a method argument.

The Basic/Standard HTTP REST API calls and their corresponding methods:

Endpoint Method
(GET) api/v1/entity/ getAllEntities
(GET) api/v1/entity/{entityId} getEntityById
(POST) api/v1/entity createEntity
(PUT) api/v1/entity/{entityId} updateEntityById
(DELETE) api/v1/entity/{entityId} deleteEntityById

For code implementation example(s) check: TeamController.java or CupController.java

3.5.1 Extra CRUD Methods/Endpoints

  • Once the basic CRUD methods are added to the Service/Controller layers, if the entity has relationships with other entities... Extra CRUD methods can be implemented.
  • @ManyToOne: logic in Child Service layer
  • @OneToOne: logic is either not required OR in the Dependent entity service layer (e.g. Student Mentor exists if Student does)
  • @ManyToMany: logic in either side OR in the Dependent side (if applicable)

The Extra HTTP REST API calls for entity rships:

Entity Relationship Endpoints Extra CRUD Methods
@ManyToOne (GET) api/v1/child/parent/{parentId}
(PUT) api/v1/child/{childId}/parent/{parentId}
(DELETE) api/v1/child/{childId}/parent/{parentId}
viewAllChildrenForParent
addChildToParent
removeChildFromParent
@OneToOne (GET) api/v1/dependent/independent/{independentId}
(PUT)api/v1/dependent/{dependentId}/independent/{independentId}
(DELETE) api/v1/dependent/{dependentId}/independent/{independentId}
viewDependentForIndependent
addDependentToIndependent
removeDependentFromIndependent
@ManyToMany (GET) api/v1/dependent/independent/{independentId}
(PUT)api/v1/dependent/{dependentId}/independent/{independentId}
(DELETE) api/v1/dependent/{dependentId}/independent/{independentId}
viewAllDependentsForIndependent
addDependentToIndependent
removeDependentFromIndependent

For code implementation example(s) check:
@ManyToOne (League) - TeamController.java
@OneToOne (Team) - OwnerController.java
@ManyToMany (Team) - CupController.java

NOTE: Service layer logic differs between @ManyToMany and @ManyToOne relationships

For example -

@ManyToOne: To check if Child contains Parent (checking an Object)

if (Objects.nonNull(player.getTeam()))

@ManyToMany: To check if Dependent contains Independent (checking a Collection of Objects)

if (cup.getTeams.contains(team))

4. Extra Features to Consider

4.1 Documentation

For code implementation example(s) check: TeamController.java and SwaggerConfig.java

4.2 Logging

  • Server logs record the activities or events that the system is performing at any given point in time.
  • Each log entry contains information such as the timestamp, the actual method being called, a custom log message, and other contextual information.
  • Each log entry also includes an identifier called a logging level.
  • Logging in Spring Boot easily done without having to declare a constant logger class by using @Slf4j Annotation.
  • Logs made in Exception Handler, Controller layer and Service layer.
  • Can configure Logback (default for SB) settings in application.properties or elsewhere.
  • Log Levels: ERROR, WARN, INFO, DEBUG, TRACE

For details on implementation: Logging in Spring Boot with SLF4J (StackAbuse)
For details on best practices: Logging Best Practices (tuhrig)

For code implementation example(s) check: TeamController.java, TeamService.java, ApiExceptionHandler.java and application.properties

4.3 Testing

There are 4 levels to Testing:

Testing Levels Description
Unit Testing Test individual components
Integration Testing Test integrated components
System Testing Test entire system
Acceptance Testing Test final system

Testing among different parties is divided into 4 parts:

Acronym Stage Party
DEV Development Software developer
SIT System Integration Test Software developer and QA engineer
UAT User Acceptance Test Client
PROD Production Public user

(more details)

For code implementation example(s) check:

Unit Test(s) Integration Test System (Integration) Test
Controller Layer MockMvc (Server-side) TestRestTemplate (Client-side)
Service Layer Cucumber Cucumber
Repository Layer

There are different agile development philosophies, namely:

Test Driver Development (TDD) Behaviour Driven Development (BDD)
Focused on testing smaller pieces of functionality in isolation Designed to test an application's behavior from the end user's standpoint
Arrange, Act, Assert (AAA) approach (synonymous with GWT) Given, When, Then (GWT) approach (synonymous with AAA)
Reduces the time required for project development Very useful in business environments
Developers write the tests Automated specifications are created by users or testers (with developers wiring them to the code under test)

Unit Testing

Unit Testing in SB

Why Unit Test these layers?

Layer Reason
Endpoints/Controllers Ensure several component work correctly, request handled correctly and data is returned in the correct structure.
Services Ensure business logic works correctly.
Repositories Ensure specifications or relations have been implemented correctly.
  • Unit Test: Controller(s) & Service(s) (& sometimes Repositories) via JUnit & Mockito.

Integration Testing

Integration Testing in SB

  • For integration testing of a Spring Boot application, we need to use @SpringBootTest along with MockMvc/RestTemplate/TestRestTemplate. Can also use Cucumber/Gherkin.
  • These tests cover the whole path through the application. We send a request to the application, check it responds correctly and has changed the DB state as intended.

NOTE: NO MOCKING INVOLVED!

(Narrow) Integration Tests (System) Integration Tests
The Spring Boot test slices like @WebMvcTest or @DataJpaTest that we saw earlier are narrow integration tests. They only load part of the application context and allow mocking of unneeded dependencies. Broad integration tests, like TestRestTemplate, that need the whole application running and exercise the application through UI or network calls. Some call these system tests or end-to-end tests to make the distinction.

4.4 Security

  • Security is a very detailed topic and has a variety of implementations, therefore a separate project dedicated to Security was created.
  • Spring Security Project

4.5 Lombok - JPA

When working with JPA and Lombok, remember these rules:

  • Avoid using @EqualsAndHashCode and @Data with JPA entities
  • Always exclude lazy attributes when using @ToString
  • Don’t forget to add @NoArgsConstructor to entities with @Builder or @AllArgsConstructor.

NOTE: Using @Data with DTO's is okay

Source: Lombok and JPA: What Could Go Wrong?

For code implementation example(s) check: Team.java and TeamDTO.java

About

FCMS is a Spring Boot REST API for dealing with the management of Football Clubs. It also serves as a comprehensive guide for building Spring Boot REST APIs.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published