π Generic CRUD Foundation & Shared Utilities Library
PlaceLive-Common-Library is the foundational shared library that powers all PlaceLive microservices with standardized CRUD operations, global exception handling, and common utilities. This library eliminates code duplication across microservices by providing a generic service architecture where any microservice can inherit complete database operations by simply extending the GenericService class. This revolutionary approach saved 1000+ lines of code in each microservice and centralized database communication patterns for consistent, maintainable development.
- Universal Service Base: One
GenericServiceimplementation handles all CRUD operations for any entity - Automatic Repository Integration: Seamless integration with Spring Data JPA repositories
- Pagination Support: Built-in pagination with filtering and search capabilities
- Dynamic Query Building: Intelligent query construction using JPA Specifications
- Type-Safe Operations: Generic type parameters ensure compile-time safety
- Centralized Error Management: All microservices use consistent exception handling
- Custom Exception Types: Pre-built exceptions for common scenarios (NotFound, BadRequest, ResourceExists)
- Standardized Error Responses: Uniform error response format across all services
- Automatic Error Codes: Built-in error code management with meaningful messages
- 1000+ Lines Saved Per Service: Each microservice saves massive amounts of boilerplate code
- Centralized Database Logic: Single point of maintenance for all database operations
- Consistent Implementation: All services follow the same patterns and conventions
- Rapid Development: New microservices can be built in hours instead of days
- Dynamic Filtering: Support for complex filtering with multiple criteria
- Pattern-Based Search: Regex-based query parsing for flexible search operations
- Multi-Field Operations: Support for various operations (equality, comparison, like, etc.)
- Specification Building: Automatic JPA Specification construction from search criteria
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Microservice Layer β
βββββββββββββββββββ¬ββββββββββββββββββ¬ββββββββββββββββββββββββββ€
β User Service β Geofencing Svc β Tracker Service β
β β β β
β extends β extends β extends β
β GenericService β GenericService β GenericService β
βββββββββββββββββββ΄ββββββββββββββββββ΄ββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β PlaceLive-Common-Library β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β GenericService<T,R> β Global Exception Handling β
β - createObject() β - BadRequestException β
β - objectsIdGet() β - NotFoundException β
β - objectsIdPut() β - ResourceAlreadyExistException β
β - deleteObject() β - GlobalExceptionHandler β
β - getListOfObjects() β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β GenericSpecification β Common DTOs β
β - Dynamic Query Builder β - ResponseDto<T> β
β - SearchCriteria β - PaginatedDto<T> β
β - Pattern Matching β - ResponseListDto<T> β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Database Layer β
β PostgreSQL + JPA/Hibernate + Spring Data β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- Framework: Spring Boot 3.4.3 with Spring Data JPA
- Database: PostgreSQL with JPA/Hibernate
- Build Tool: Maven with GitHub Packages deployment
- Validation: Spring Boot Starter Validation
- Utilities: Lombok, MapStruct, Jackson
- Documentation: Swagger/OpenAPI annotations
public interface GenericService<T> {
String deleteObject(Integer id);
T createObject(T object);
T objectsIdPut(Integer id, T object);
T objectsIdGet(Integer id);
Page<T> getListOfObjects(Integer page, Integer size, String filter, String search);
}@Service
public class GenericServiceImpl<T, R extends JpaRepository<T, Long> & JpaSpecificationExecutor<T>>
implements GenericService<T> {
protected final R repository;
public GenericServiceImpl(R repository) {
this.repository = repository;
}
@Override
public String deleteObject(Integer id) {
if (id == null || id <= 0) {
throw new BadRequestException(ErrorCode.BAD0001.getCode(), ErrorCode.BAD0001.getMessage());
}
try {
this.repository.deleteById(id.longValue());
return ErrorCode.OK200.getCode();
} catch (EmptyResultDataAccessException ex) {
throw new NotFoundException(ErrorCode.ERR404.getCode(), ErrorCode.ERR404.getMessage());
}
}
@Override
public T createObject(T object) {
return repository.save(object);
}
@Override
public T objectsIdPut(Integer id, T object) {
if (id == null || id <= 0) {
throw new BadRequestException(ErrorCode.BAD0001.getCode(), ErrorCode.BAD0001.getMessage());
}
repository.findById(Long.valueOf(id))
.orElseThrow(() -> new NotFoundException(ErrorCode.ERR404.getCode(), ErrorCode.ERR404.getMessage()));
return repository.save(object);
}
@Override
public T objectsIdGet(Integer id) {
if (id == null || id <= 0) {
throw new BadRequestException(ErrorCode.BAD0001.getCode(), ErrorCode.BAD0001.getMessage());
}
return repository.findById(Long.valueOf(id))
.orElseThrow(() -> new NotFoundException(ErrorCode.ERR404.getCode(), ErrorCode.ERR404.getMessage()));
}
@Override
public Page<T> getListOfObjects(Integer page, Integer size, String filter, String search) {
// Advanced query building logic (see implementation details below)
}
}The library includes a sophisticated query building system that parses filter and search parameters:
// Pattern: key-operation-value
// Examples: "name:john", "age>25", "status!=active"
Pattern pattern = Pattern.compile("([a-zA-Z0-9_]+)(:|!=|>|<)([^,]+)");
// Combined filters: "name:john,age>25,status:active"
String combinedQuery = Stream.of(filter, search)
.filter(Objects::nonNull)
.filter(s -> !s.isEmpty())
.collect(Collectors.joining(","));public class SearchCriteria {
private String key; // Field name (e.g., "firstName", "age")
private String operation; // Operation (e.g., ":", ">", "<", "!=")
private Object value; // Search value (e.g., "john", "25")
public SearchCriteria(String key, String operation, Object value) {
this.key = key;
this.operation = operation;
this.value = value;
}
}public class GenericSpecification<T> implements Specification<T> {
private final SearchCriteria searchCriteria;
@Override
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
return handleSimplePredicate(root, builder);
}
private Predicate createPredicateForField(Path<?> path, CriteriaBuilder builder) {
if (path.getJavaType() == String.class) {
if (searchCriteria.getOperation().equals(":")) {
return builder.like(path.as(String.class), "%" + searchCriteria.getValue() + "%");
} else {
return builder.equal(path, searchCriteria.getValue());
}
} else {
return builder.equal(path, searchCriteria.getValue());
}
}
}// BadRequestException.java
public class BadRequestException extends RuntimeException {
private String errorCode;
public BadRequestException(String errorCode, String message) {
super(message);
this.errorCode = errorCode;
}
}
// NotFoundException.java
public class NotFoundException extends RuntimeException {
private String errorCode;
public NotFoundException(String errorCode, String message) {
super(message);
this.errorCode = errorCode;
}
}
// ResourceAlreadyExistException.java
public class ResourceAlreadyExistException extends RuntimeException {
private String errorCode;
public ResourceAlreadyExistException(String errorCode, String message) {
super(message);
this.errorCode = errorCode;
}
}public enum ErrorCode {
OK200("200", "Success"),
BAD0001("BAD0001", "Invalid request parameters"),
ERR404("ERR404", "Resource not found"),
ERR409("ERR409", "Resource already exists");
private final String code;
private final String message;
ErrorCode(String code, String message) {
this.code = code;
this.message = message;
}
// Getters...
}@Getter
@Setter
public class ResponseDto<T> {
private boolean success;
private String message;
private T data;
private String errorCode;
private LocalDateTime timestamp;
public static <T> ResponseDto<T> success(T data) {
ResponseDto<T> response = new ResponseDto<>();
response.setSuccess(true);
response.setData(data);
response.setTimestamp(LocalDateTime.now());
return response;
}
public static <T> ResponseDto<T> error(String errorCode, String message) {
ResponseDto<T> response = new ResponseDto<>();
response.setSuccess(false);
response.setErrorCode(errorCode);
response.setMessage(message);
response.setTimestamp(LocalDateTime.now());
return response;
}
}@Getter
@Setter
public class PaginatedDto<T> {
private List<T> content;
private int page;
private int size;
private long totalElements;
private int totalPages;
private boolean first;
private boolean last;
public static <T> PaginatedDto<T> of(Page<T> page) {
PaginatedDto<T> dto = new PaginatedDto<>();
dto.setContent(page.getContent());
dto.setPage(page.getNumber());
dto.setSize(page.getSize());
dto.setTotalElements(page.getTotalElements());
dto.setTotalPages(page.getTotalPages());
dto.setFirst(page.isFirst());
dto.setLast(page.isLast());
return dto;
}
}<dependency>
<groupId>com.jlss.placelive</groupId>
<artifactId>placelive-common-library</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
private String email;
private LocalDateTime createdAt;
// Getters and Setters...
}@Repository
public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
// Custom methods if needed
}@Service
public class UserService extends GenericServiceImpl<User, UserRepository> {
public UserService(UserRepository repository) {
super(repository);
}
// All CRUD operations are automatically available!
// Additional custom methods can be added here
public User findByEmail(String email) {
return repository.findByEmail(email);
}
}@RestController
@RequestMapping("/api/v1/users")
public class UserController {
@Autowired
private UserService userService;
@PostMapping
public ResponseEntity<ResponseDto<User>> createUser(@RequestBody User user) {
User created = userService.createObject(user);
return ResponseEntity.ok(ResponseDto.success(created));
}
@GetMapping("/{id}")
public ResponseEntity<ResponseDto<User>> getUser(@PathVariable Integer id) {
User user = userService.objectsIdGet(id);
return ResponseEntity.ok(ResponseDto.success(user));
}
@GetMapping
public ResponseEntity<ResponseDto<PaginatedDto<User>>> getUsers(
@RequestParam(defaultValue = "0") Integer page,
@RequestParam(defaultValue = "10") Integer size,
@RequestParam(required = false) String filter,
@RequestParam(required = false) String search) {
Page<User> users = userService.getListOfObjects(page, size, filter, search);
return ResponseEntity.ok(ResponseDto.success(PaginatedDto.of(users)));
}
@PutMapping("/{id}")
public ResponseEntity<ResponseDto<User>> updateUser(@PathVariable Integer id, @RequestBody User user) {
User updated = userService.objectsIdPut(id, user);
return ResponseEntity.ok(ResponseDto.success(updated));
}
@DeleteMapping("/{id}")
public ResponseEntity<ResponseDto<String>> deleteUser(@PathVariable Integer id) {
String result = userService.deleteObject(id);
return ResponseEntity.ok(ResponseDto.success(result));
}
}GET /api/v1/users?filter=firstName:john,age>25,status:active&search=email:gmail.comThis query will find users where:
firstNamecontains "john" (case-insensitive)ageis greater than 25statusequals "active"emailcontains "gmail.com"
:- Contains/Like operation for strings, equals for other types>- Greater than or equal to<- Less than or equal to!=- Not equal to (can be extended)
- Before Common Library: Each microservice had 1000+ lines of CRUD code
- After Common Library: Each microservice has ~50 lines of service code
- Total Lines Saved: 4000+ lines across 4 microservices
- Development Time: Reduced from days to hours for new services
- Maintenance Effort: Centralized updates affect all services instantly
- Rapid Prototyping: New microservices can be built in 2-3 hours
- Consistent Patterns: All services follow the same architecture
- Reduced Testing: Common functionality tested once, used everywhere
- Bug Fixes: Fix once in library, applied to all services
// Automatic pagination with Spring Data
Page<User> users = userService.getListOfObjects(0, 10, "status:active", "name:john");
// Response includes:
// - Current page content
// - Total elements count
// - Total pages
// - First/Last page indicators// Multiple criteria are automatically combined with AND
Specification<User> spec = null;
while(matcher.find()) {
SearchCriteria criteria = new SearchCriteria(
matcher.group(1), matcher.group(2), matcher.group(3)
);
GenericSpecification<User> genericSpec = new GenericSpecification<>(criteria);
spec = spec == null ?
Specification.where(genericSpec) :
spec.and(genericSpec);
}{
"success": false,
"message": "User not found",
"data": null,
"errorCode": "ERR404",
"timestamp": "2024-01-01T12:00:00"
}- OR Operation Support: Extend query builder to support OR operations
- Join Query Support: Handle relationships and nested queries
- Caching Integration: Built-in Redis caching for common queries
- Audit Trail: Automatic creation/modification tracking
- Soft Delete: Built-in soft delete functionality
- Validation Rules: Common validation patterns for all entities
- Query Optimization: Automatic query optimization suggestions
- Index Recommendations: Database index recommendations based on query patterns
- Batch Operations: Support for bulk create/update/delete operations
- Connection Pooling: Optimized database connection management
src/
βββ main/java/com/jlss/placelive/commonlib/
β βββ dto/ # Common DTOs
β β βββ PaginatedDto.java
β β βββ ResponseDto.java
β β βββ ResponseListDto.java
β βββ enums/ # Error codes and constants
β β βββ ErrorCode.java
β βββ exceptions/ # Custom exceptions
β β βββ handler/ # Global exception handlers
β β βββ BadRequestException.java
β β βββ NotFoundException.java
β β βββ ResourceAlreadyExistException.java
β βββ service/ # Generic service layer
β β βββ impl/
β β β βββ GenericServiceImpl.java
β β βββ GenericService.java
β βββ specification/ # Query building
β βββ impl/
β βββ GenericSpecification.java
β βββ SearchCriteria.java
βββ main/resources/
βββ application.yml
<groupId>com.jlss.placelive</groupId>
<artifactId>placelive-common-library</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<java.version>17</java.version>
<spring-boot.version>3.4.3</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<distributionManagement>
<repository>
<id>github</id>
<name>GitHub Packages</name>
<url>https://maven.pkg.github.com/JLSS-virtual/PlaceLive-Common-Library</url>
</repository>
</distributionManagement>- Automatic Validation: Built-in validation for all operations
- SQL Injection Prevention: JPA Specifications prevent SQL injection
- Parameter Sanitization: Automatic sanitization of search parameters
- Type Safety: Generic types ensure compile-time type checking
- Consistent Error Format: All services return the same error structure
- Meaningful Error Codes: Human-readable error codes for better debugging
- Logging Integration: Comprehensive logging for all operations
- No Stack Trace Exposure: Clean error responses for production
- New Service Creation: From 3-5 days to 2-3 hours
- Code Consistency: All services follow identical patterns
- Testing Efficiency: Test common library once, benefits all services
- Bug Resolution: Fix once, deploy everywhere
- Centralized Updates: Database logic updates affect all services
- Consistent Behavior: All services behave identically for common operations
- Reduced Technical Debt: Single source of truth for CRUD operations
- Simplified Onboarding: New developers learn one pattern, apply everywhere
- Faster Time to Market: Rapid service development
- Lower Development Costs: Reduced developer hours for common tasks
- Higher Code Quality: Tested, proven patterns used everywhere
- Scalable Architecture: Easy to add new services with minimal effort
- Backward Compatibility: All changes must maintain backward compatibility
- Performance Focus: Any changes should improve or maintain performance
- Documentation First: Update documentation before code changes
- Test Coverage: Maintain 90%+ test coverage for all new features
- Generic Design: Ensure all features work for any entity type
// Example: Adding soft delete functionality
public interface SoftDeletable {
LocalDateTime getDeletedAt();
void setDeletedAt(LocalDateTime deletedAt);
boolean isDeleted();
}
// Extend GenericService for soft delete support
public String softDeleteObject(Integer id) {
T entity = objectsIdGet(id);
if (entity instanceof SoftDeletable) {
((SoftDeletable) entity).setDeletedAt(LocalDateTime.now());
repository.save(entity);
return ErrorCode.OK200.getCode();
}
return deleteObject(id);
}This project is licensed under the MIT License - see the LICENSE file for details.
- Documentation: Wiki
- Issues: GitHub Issues
- Feature Requests: GitHub Discussions
- Email: jlss.virtual.0808@gmail.com
PlaceLive-Common-Library: The foundation that revolutionized PlaceLive's microservice development, eliminating thousands of lines of repetitive code while ensuring consistency, maintainability, and rapid development across the entire ecosystem. πβ‘