# Validation, and CQRS Pattern Alignment - JoseCanova/brainz GitHub Wiki
Validation, and CQRS Pattern Alignment
Introduction
In this project, we will follow the CQRS (Command Query Responsibility Segregation) pattern. This design principle separates the domain model used for writing (commands) from the projection models used for reading (queries). In our implementation:
- Entities (like
Genre
) are used for persistence and mutation. - Records (like
GenreRecord
) serve as immutable DTOs for projections and SQL-DML commands and in the case of ORM Mapping Select queries shall be considered with caution just in "select before update queries if still necessary" , so consider a segregation of responsabilities and a specilization of SQL-DML, Select commands from now and beyond are DQL commands.
This separation makes the codebase more robust, maintainable, and ready for future complex business logic.
SQL Data Manipulation Language (DML)
SQL Data Manipulation Language (DML) commands are used to manage and query data within a database, including SELECT, INSERT, UPDATE, and DELETE. These commands allow users to retrieve, add, modify, and remove data from tables, but they do not alter the structure of the database itself.
Key DML Commands:
- SELECT: This command is used to retrieve data from one or more tables in a database. It is used to access information stored within the database.
- INSERT: Used to add new rows (data) into an existing table.
- UPDATE: Modifies existing data within the rows of a table.
- DELETE: Removes one or more rows from a table.
While SELECT is often considered a DQL (Data Query Language) command, it is frequently grouped with DML due to its extensive use in manipulating data within the database. Other commands like MERGE (which can perform UPDATE, INSERT, and DELETE actions atomically) are also considered DML operations in some contexts. It's important to note that DML commands are not auto-committed, meaning changes are not permanently saved until a commit operation is performed, allowing for rollbacks if needed.
Importance of Record Validation
When mapping data between the relational model (RDBMS) and the Java application, it is critical that constraints defined at the database level are mirrored in our Java record
classes. This is enforced using validation annotations (from jakarta.validation.constraints
), such as @NotNull
and @NotEmpty
.
Why Align Validation?
- Integrity: Prevents invalid data (such as nulls for NOT NULL columns) from reaching the persistence layer.
- Consistency: Ensures that the Java projection model (
record
) and the database schema evolve together. - Service Layer Guarantee: The service layer (see
GenreService.java
) can validate incoming DTOs/records before converting or saving to the database.
Example: Genre Entity and Record
Entity:
// Genre.java
@Column(name="genreId", columnDefinition = "bigint , not null")
private Long genreId;
@Column(name="genreName" , columnDefinition = "varchar , not null")
private String genreName;
@Column(name="gid" , columnDefinition = "varchar , not null")
private UUID gid;
Record (with validation):
// GenreRecord.java
public record GenreRecord(
@NotNull Long genreId,
@NotEmpty UUID gid,
@NotEmpty String genreName
) implements GenreEntity { ... }
Key Point:
If a column is NOT NULL
in the database, the corresponding field in the record should be annotated with @NotNull
. If a string should not be empty, use @NotEmpty
.
Validation in the Service Layer
The service (e.g., GenreService.java
) is responsible for validating records before they are persisted. Spring's validation mechanism automatically checks the annotations on the record fields when you use @Valid
or manually invoke the validator.
Example:
public void createGenre(@Valid GenreRecord genreRecord) {
// Spring will validate based on annotations in GenreRecord
Genre genre = converter.convertValue(genreRecord, Genre.class);
genreRepository.save(genre);
}
@SpringBootTest
public class BrainzValidatorConfigurationTest {
@Autowired
Validator validator;
@Test
public void validatorInjectionTest() {
assertNotNull(validator);
GenreRecord genreRecord = new GenreRecord(null ,
UUID.randomUUID(),
"genre name");
Set<?> violations = validator.validate(genreRecord, Default.class);
assertTrue (violations.size()==1);
}
}
Best Practices Checklist
- Align record field types and nullability with the DB schema.
- Use validation annotations on record parameters (
@NotNull
,@NotEmpty
). - Validate records in the service layer before persistence.
- Update records and validation if DB schema changes.
Conclusion
Proper alignment of the record
declaration, validation annotations, and the relational model is crucial for data integrity and the maintainability of the CQRS architecture. Always keep projections (record
), entities, and validation in sync to avoid runtime errors and ensure business rules are enforced at every layer.