In software engineering, validation is an essential step, especially when handling user input or ensuring compliance with business rules. Although it’s common to use exceptions to signal errors, this approach can be ineffective for expected issues, such as empty fields or invalid formats.
The Notification Pattern addresses this problem by collecting all errors in a notification object, allowing them to be handled in a structured way and presented to the user clearly and efficiently.
What is the Notification Pattern?
The Notification Pattern is a design strategy that collects validation errors rather than throwing exceptions immediately. It allows your application to gather multiple issues in one go and report them collectively, which is especially useful when validating complex domain objects.
This approach provides users with comprehensive feedback and developers with cleaner, more maintainable code.
Why Use the Notification Pattern?
Prevents Overuse of Exceptions: Exceptions should signal unexpected errors. Validation errors, however, are expected and should be handled differently.
Better User Feedback: Multiple validation errors can be collected and displayed together, preventing a frustrating “whack-a-mole” experience for users.
Decoupling Logic: By using the Notification Pattern, validation logic is separated from the flow control of the program, making it easier to maintain.
How the Notification Pattern Works
The Notification Pattern introduces a structured flow to handling validation in your domain models. Rather than interrupting execution with exceptions, it captures and organizes all issues for smarter downstream handling.
Notification Object
A centralized container that holds a list of validation errors. It acts as an error aggregator across multiple fields or objects within the domain logic.
Validation Phase
Instead of immediately throwing exceptions when a validation rule fails, the rule appends an error message to the notification object. This continues for all validations, allowing the system to collect every issue in one pass.
Error Handling
Once all validations are complete, the application checks the notification object. If errors are present, a domain-specific exception (like DomainViolationException) can be thrown, or the errors can be handled gracefully.
Sample Implementation
Structure of the Notification Pattern
The Notification Pattern is composed of three main parts that work together to validate domain models without immediately throwing exceptions:
Validation Interface
A common contract that defines how individual validation rules behave.Custom Exception
An exception that wraps all collected violations into a single throwable unit, allowing for comprehensive error reporting.Notification Object
An accumulator that gathers all validation errors for a given domain object.
Validation Interface
public interface DomainValidation {
<T> String getErrorMessage(T obj);
public abstract <T> boolean isValid(T obj);
}
Custom Exception
public class DomainViolationException extends RuntimeException {
private Set<Violation> violations;
public DomainViolationException(Class claz) {
super(String.format("Violation of Domain rules in object %s", claz.getSimpleName()));
this.violations = new HashSet<>();
}
public void addViolation(Violation violation) {
this.violations.add(violation);
}
public Set<Violation> getViolations() {
return violations;
}
}
Notification Object
public class DomainExceptionNotification {
private DomainViolationException exception;
public <T> DomainExceptionNotification(Class<T> claz) {
this.exception = new DomainViolationException(claz);
}
public <T> T validateAndSet(String objName, T obj, DomainValidation violation) {
if (!violation.isValid(obj)) {
this.exception.addViolation(new DomainViolationException.Violation(objName, violation.getErrorMessage(obj)));
}
return obj;
}
public void validateDomainObject() {
if (!this.exception.getViolations().isEmpty()) {
throw this.exception;
}
}
public enum ValidationEnum implements DomainValidation {
NULL("field cannot be null") {
@Override
public <T> boolean isValid(T obj) {
return nonNull(obj);
}
},
BLANK("field cannot be blank") {
@Override
public <T> boolean isValid(T obj) {
return nonNull(obj) && !isBlank((String) obj);
}
};
ValidationEnum(String errorMessage) {
this.errorMessage = errorMessage;
}
private final String errorMessage;
@Override
public <T> String getErrorMessage(T obj) {
return errorMessage;
}
@Override
public abstract <T> boolean isValid(T obj);
}
}
Applying to domain objects
Athlete Domain Object
Validates required fields (personalData and category) before creation.
public class Athlete {
private PersonalData personalData;
private Category category;
private Athlete(String athleteId,
PersonalData personalData) {
super(athleteId);
DomainExceptionNotification notification = new DomainExceptionNotification(Athlete.class);
this.personalData = notification.validateAndSet("personalData", personalData, DomainExceptionNotification.ValidationEnum.NULL);
if (nonNull(personalData)) {
this.category = notification.validateAndSet("category", Category.findCategoryFromAge(personalData().age()), DomainExceptionNotification.ValidationEnum.NULL);
}
notification.validateDomainObject();
}
}
Contact Domain Object
Validates the email field using a custom rule.
public class Contact {
private String email;
private String mobileNumber;
private String address;
private Contact(String email,
String mobileNumber,
String address) {
DomainExceptionNotification notification = new DomainExceptionNotification(Contact.class);
this.email = notification.validateAndSet("email", email, new EmailValidation());
this.mobileNumber = mobileNumber;
this.address = address;
notification.validateDomainObject();
}
public static Contact of(final String email,
final String mobileNumber,
final String address) {
return new Contact(
email,
mobileNumber,
address
);
}
}
Conclusion
The Notification Pattern is a powerful technique for handling validation in domain-driven applications. It provides:
Cleaner code by avoiding excessive exception handling.
Improved feedback by reporting all errors at once.
Better maintainability through separation of validation and flow control.
By structuring your domain logic around notifications instead of exceptions, your applications become more robust, more user-friendly, and easier to evolve.
Additional Resources
For a step-by-step video walkthrough of this example and further explanation of the pattern in action, watch the full tutorial:
🟥▶️https://www.youtube.com/watch?v=pxpN4WJtrjU
Remember, real speed doesn’t come from rushing. It comes from doing things right. As Robert C. Martin said, “The only way to go fast is to do things well.”
References
Fowler, M. (n.d.). Notification. MartinFowler.com. https://martinfowler.com/eaaDev/Notification.html
BurnedPanic. notification-pattern. GitHub, https://github.com/BurnedPanic/notification-pattern
Top comments (0)