mirror of
https://github.com/tiennm99/java-design-patterns.git
synced 2026-05-16 04:58:46 +00:00
feat: added notification pattern (#2629)
Co-authored-by: Ilkka Seppälä <iluwatar@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
package com.iluwatar;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* The notification pattern captures information passed between layers, validates the information, and returns
|
||||
* any errors to the presentation layer if needed.
|
||||
*
|
||||
* <p>In this code, this pattern is implemented through the example of a form being submitted to register
|
||||
* a worker. The worker inputs their name, occupation, and date of birth to the RegisterWorkerForm (which acts
|
||||
* as our presentation layer), and passes it to the RegisterWorker class (our domain layer) which validates it.
|
||||
* Any errors caught by the domain layer are then passed back to the presentation layer through the
|
||||
* RegisterWorkerDto.</p>
|
||||
*/
|
||||
public class App {
|
||||
|
||||
private static final String NAME = "";
|
||||
private static final String OCCUPATION = "";
|
||||
private static final LocalDate DATE_OF_BIRTH = LocalDate.of(2016, 7, 13);
|
||||
|
||||
public static void main(String[] args) {
|
||||
var form = new RegisterWorkerForm(NAME, OCCUPATION, DATE_OF_BIRTH);
|
||||
form.submit();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.iluwatar;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Layer super type for all Data Transfer Objects.
|
||||
* Also contains code for accessing our notification.
|
||||
*/
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
public class DataTransferObject {
|
||||
|
||||
private final Notification notification = new Notification();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.iluwatar;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* The notification. Used for storing errors and any other methods
|
||||
* that may be necessary for when we send information back to the
|
||||
* presentation layer.
|
||||
*/
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
public class Notification {
|
||||
|
||||
private final List<NotificationError> errors = new ArrayList<>();
|
||||
|
||||
public boolean hasErrors() {
|
||||
return !this.errors.isEmpty();
|
||||
}
|
||||
|
||||
public void addError(NotificationError error) {
|
||||
this.errors.add(error);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.iluwatar;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* Error class for storing information on the error.
|
||||
* Error ID is not necessary, but may be useful for serialisation.
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public class NotificationError {
|
||||
private int errorId;
|
||||
private String errorMessage;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Error " + errorId + ": " + errorMessage;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.iluwatar;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.Period;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Class which handles actual internal logic and validation for worker registration.
|
||||
* Part of the domain layer which collects information and sends it back to the presentation.
|
||||
*/
|
||||
@Slf4j
|
||||
public class RegisterWorker extends ServerCommand {
|
||||
static final int LEGAL_AGE = 18;
|
||||
protected RegisterWorker(RegisterWorkerDto worker) {
|
||||
super(worker);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the data provided and adds it to the database in the backend.
|
||||
*/
|
||||
public void run() {
|
||||
|
||||
validate();
|
||||
if (!super.getNotification().hasErrors()) {
|
||||
LOGGER.info("Register worker in backend system");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates our data. Checks for any errors and if found, adds to notification.
|
||||
*/
|
||||
private void validate() {
|
||||
var ourData = ((RegisterWorkerDto) this.data);
|
||||
//check if any of submitted data is not given
|
||||
// passing for empty value validation
|
||||
fail(isNullOrBlank(ourData.getName()), RegisterWorkerDto.MISSING_NAME);
|
||||
fail(isNullOrBlank(ourData.getOccupation()), RegisterWorkerDto.MISSING_OCCUPATION);
|
||||
fail(isNullOrBlank(ourData.getDateOfBirth()), RegisterWorkerDto.MISSING_DOB);
|
||||
|
||||
if (isNullOrBlank(ourData.getDateOfBirth())) {
|
||||
// If DOB is null or empty
|
||||
fail(true, RegisterWorkerDto.MISSING_DOB);
|
||||
} else {
|
||||
// Validating age ( should be greater than or equal to 18 )
|
||||
Period age = Period.between(ourData.getDateOfBirth(), LocalDate.now());
|
||||
fail(age.getYears() < LEGAL_AGE, RegisterWorkerDto.DOB_TOO_SOON);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates for null/empty value.
|
||||
*
|
||||
* @param obj any object
|
||||
* @return boolean
|
||||
*/
|
||||
protected boolean isNullOrBlank(Object obj) {
|
||||
if (obj == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (obj instanceof String) {
|
||||
return ((String) obj).trim().isEmpty();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* If a condition is met, adds the error to our notification.
|
||||
*
|
||||
* @param condition condition to check for.
|
||||
* @param error error to add if condition met.
|
||||
*/
|
||||
protected void fail(boolean condition, NotificationError error) {
|
||||
if (condition) {
|
||||
super.getNotification().addError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.iluwatar;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* Data transfer object which stores information about the worker. This is carried between
|
||||
* objects and layers to reduce the number of method calls made.
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class RegisterWorkerDto extends DataTransferObject {
|
||||
private String name;
|
||||
private String occupation;
|
||||
private LocalDate dateOfBirth;
|
||||
|
||||
/**
|
||||
* Error for when name field is blank or missing.
|
||||
*/
|
||||
public static final NotificationError MISSING_NAME =
|
||||
new NotificationError(1, "Name is missing");
|
||||
|
||||
/**
|
||||
* Error for when occupation field is blank or missing.
|
||||
*/
|
||||
public static final NotificationError MISSING_OCCUPATION =
|
||||
new NotificationError(2, "Occupation is missing");
|
||||
|
||||
/**
|
||||
* Error for when date of birth field is blank or missing.
|
||||
*/
|
||||
public static final NotificationError MISSING_DOB =
|
||||
new NotificationError(3, "Date of birth is missing");
|
||||
|
||||
/**
|
||||
* Error for when date of birth is less than 18 years ago.
|
||||
*/
|
||||
public static final NotificationError DOB_TOO_SOON =
|
||||
new NotificationError(4, "Worker registered must be over 18");
|
||||
|
||||
|
||||
protected RegisterWorkerDto() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple set up function for capturing our worker information.
|
||||
*
|
||||
* @param name Name of the worker
|
||||
* @param occupation occupation of the worker
|
||||
* @param dateOfBirth Date of Birth of the worker
|
||||
*/
|
||||
public void setupWorkerDto(String name, String occupation, LocalDate dateOfBirth) {
|
||||
this.name = name;
|
||||
this.occupation = occupation;
|
||||
this.dateOfBirth = dateOfBirth;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.iluwatar;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* The form submitted by the user, part of the presentation layer,
|
||||
* linked to the domain layer through a data transfer object and
|
||||
* linked to the service layer directly.
|
||||
*/
|
||||
@Slf4j
|
||||
public class RegisterWorkerForm {
|
||||
String name;
|
||||
String occupation;
|
||||
LocalDate dateOfBirth;
|
||||
RegisterWorkerDto worker;
|
||||
RegisterWorkerService service = new RegisterWorkerService();
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param name Name of the worker
|
||||
* @param occupation occupation of the worker
|
||||
* @param dateOfBirth Date of Birth of the worker
|
||||
*/
|
||||
public RegisterWorkerForm(String name, String occupation, LocalDate dateOfBirth) {
|
||||
this.name = name;
|
||||
this.occupation = occupation;
|
||||
this.dateOfBirth = dateOfBirth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to submit the form for registering a worker.
|
||||
*/
|
||||
public void submit() {
|
||||
//Transmit information to our transfer object to communicate between layers
|
||||
saveToWorker();
|
||||
//call the service layer to register our worker
|
||||
service.registerWorker(worker);
|
||||
|
||||
//check for any errors
|
||||
if (worker.getNotification().hasErrors()) {
|
||||
indicateErrors();
|
||||
LOGGER.info("Not registered, see errors");
|
||||
} else {
|
||||
LOGGER.info("Registration Succeeded");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves worker information to the data transfer object.
|
||||
*/
|
||||
private void saveToWorker() {
|
||||
worker = new RegisterWorkerDto();
|
||||
worker.setName(name);
|
||||
worker.setOccupation(occupation);
|
||||
worker.setDateOfBirth(dateOfBirth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for any errors with form submission and show them to the user.
|
||||
*/
|
||||
public void indicateErrors() {
|
||||
worker.getNotification().getErrors().forEach(error -> LOGGER.error(error.toString()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.iluwatar;
|
||||
|
||||
/**
|
||||
* Service used to register a worker.
|
||||
* This represents the basic framework of a service layer which can be built upon.
|
||||
*/
|
||||
public class RegisterWorkerService {
|
||||
/**
|
||||
* Creates and runs a command object to do the work needed,
|
||||
* in this case, register a worker in the system.
|
||||
*
|
||||
* @param registration worker to be registered if possible
|
||||
*/
|
||||
public void registerWorker(RegisterWorkerDto registration) {
|
||||
var cmd = new RegisterWorker(registration);
|
||||
cmd.run();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.iluwatar;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
/**
|
||||
* Stores the dto and access the notification within it.
|
||||
* Acting as a layer supertype in this instance for the domain layer.
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
public class ServerCommand {
|
||||
protected DataTransferObject data;
|
||||
|
||||
/**
|
||||
* Basic getter to extract information from our data.
|
||||
*
|
||||
* @return the notification stored within the data
|
||||
*/
|
||||
public Notification getNotification() {
|
||||
return data.getNotification();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user