Taskl is a Java-based task manager RESTful API designed to demonstrate high-quality software development practices. It emphasizes simplicity ("keep it simple stupid" principle), robust security, and a rich infrastructure. Despite having only three domain entities, Taskl is engineered for easy expansion to any business requirements while preserving product and its codebase quality.
- In the project's root directory, run
docker-compose up --buildto start the application. - Open the Springdoc Swagger UI at http://localhost:8080/api/swagger-ui/index.html to explore and interact with the API endpoints.
- For quick access, locate the pre-generated authentication token in the application logs by searching for "Token for local start-up". Alternatively, use the Authentication endpoints in the Swagger UI to manually sign in and obtain a token.
- Spring Boot: Main framework for API functionality.
- Lombok: Used to minimize boilerplate; transition to Kotlin for better interoperability is planned.
- MapStruct: Facilitates the mapping between entity and DTO layers.
- Embedded MongoDB: Supports transactional operations in a multi-cluster setup for local development and testing.
- Guava: Provides additional utilities, such as multimap.
- Mustache: Enables dynamic error message templating.
- JUnit & AssertJ: Allows to write expressive Java tests.
- Docker Compose: Simplifies infrastructure setup and project start-up.
The Error Factory streamlines the creation of standardized business logic errors. By making error generation declarative, it simplifies the development process. Below is an example of defining an error template:
@ErrorTemplate(
id = "taskl.api.error.validation-failed",
httpStatus = HttpStatus.BAD_REQUEST,
summary = "Validation failed",
message = "{{message}}"
)
ApplicationError validationFailed(String message);This template produces an ApplicationError object:
public class ApplicationError {
String code;
HttpStatus httpStatus;
String summary;
String message;
}Error Factory is implemented as a singleton to ensure that application-wide errors are consistently accessible.
To create a specific error ErrorFactory.get().specificError() has to be called:
void signUp(SignUpRequest request) {
if (userRepository.existsUserByEmail(request.getEmail())) {
throw ErrorFactory.get().emailAlreadyInUse();
}
//More code
}The centralization of error handling is further enhanced by a common method that builds the response based on the generated error:
@ExceptionHandler(ApplicationError.class)
public ResponseEntity<ErrorResponse> applicationErrorHandler(ApplicationError e) {
return ResponseEntity.status(e.getHttpStatus().value()).body(e.toResponse());
}This uniform approach to error management reduces boilerplate and ensures clarity and consistency in how errors are communicated throughout the application.
The Access Service API empowers consistent and secure access to entities:
EntityT getPresentOrThrow(IdT id);
Collection<EntityT> getPresentOrThrow(Collection<IdT> ids);It also offers a Secured version of methods which allows to control entity visibility to user:
EntityT getPresentOrThrowSecured(IdT id);Well-tested base class simplifies service implementation, requiring just two access functions and a factory method:
@Override
public Function<Collection<String>, Collection<Project>> defaultAccessFunction() {
return projectRepository::findAllById;
}
@Override
public Function<Collection<String>, Collection<Project>> securedAccessFunction() {
return projectIds -> {
var currentUserId = SecurityUtil.getCurrentUserId();
return projectRepository.findProjectsByIdInAndMemberUserIdsContains(projectIds, currentUserId);
};
}
@Override
public Supplier<ApplicationError> notFoundExceptionSupplier() {
return ErrorFactory.get()::projectNotFound;
}This setup enhances developer experience by simplifying local setup and supporting transactional operations without the need for a Docker daemon.
UUID v7 is utilized for entity identification to boost security and performance. The benefits include:
- Reduced Collision Risk: The unique structure of UUID v7 lowers the chance of ID collisions, reducing security risks.
- Temporal Sequencing: Adds a timestamp to each ID for chronological sorting, maintaining database indexing efficiency.
- Performance Efficiency: Offers fast performance similar to MongoDB ObjectId, for more see UUID7 vs UUID4 Performance Analysis, ID Generation Benchmarks.
GitHub's workflows are used to maintain code quality, running tests and lint checks on every pull request to ensure the health of the codebase.
- Test suite achieves 87% line coverage and 91% method coverage.
- Integration tests are prioritized over pure unit tests to ensure reliability and system integrity.
- Each test running in a separate transaction for isolation.
- Migrate from Lombok to Kotlin for enhanced language features and interoperability.
- Transition build scripts to Kotlin DSL for Gradle.
- Integrate SonarQube for continuous code quality checks.
- Implement a Jacoco workflow for automated coverage reporting.
- Configure environment-specific database setups, including a standalone MongoDB cluster.
- Plan deployment to Google Cloud and manage infrastructure as code with Terraform.
- Expand OAuth 2.0 registration options for enhanced security.