|
| 1 | +title: Notification |
| 2 | +category: Behavioural |
| 3 | +language: en |
| 4 | +tags: |
| 5 | +- Decoupling |
| 6 | +- Presentation |
| 7 | +- Domain |
| 8 | +--- |
| 9 | + |
| 10 | +## Intent |
| 11 | + |
| 12 | +To capture information about errors and other information that occurs in the domain layer |
| 13 | +which acts as the internal logic and validation tool. The notification then communicates this information |
| 14 | +back to the presentation for handling and displaying to the user what errors have occurred and why. |
| 15 | + |
| 16 | +## Explanation |
| 17 | + |
| 18 | +Real world example |
| 19 | + |
| 20 | +> You need to register a worker for your company. The information submitted needs to be valid |
| 21 | +> before the worker can be added to the database, and if there are any errors you need to know about them. |
| 22 | +
|
| 23 | +In plain words |
| 24 | + |
| 25 | +> A notification is simply a way of collecting information about errors and communicating it to the user |
| 26 | +> so that they are aware of them |
| 27 | +
|
| 28 | +**Programatic example** |
| 29 | +Building off the example of registering a worker for a company, we can now explore a coded example for a full picture |
| 30 | +of how this pattern looks when coded up. For full code please visit the github repository. |
| 31 | + |
| 32 | +To begin with, the user submits information to a form through the presentation layer of our software. The information |
| 33 | +given to register a worker is the worker's name, occupation, and date of birth. The program will then make sure none of |
| 34 | +these fields are blank (validation) and that the worker is over 18 years old. If there are any errors, |
| 35 | +the program will inform the user. |
| 36 | + |
| 37 | +The code for the form is given below. This form acts as our presentation layer, taking input from the user and printing |
| 38 | +output using a LOGGER (in this case). The form then gives this information to the service layer RegisterWorkerService |
| 39 | +through a data transfer object (DTO). |
| 40 | + |
| 41 | +The service handles information validation and the presentation can then check the notification stored within the DTO for |
| 42 | +any errors and display them to the user if necessary. Otherwise, it will inform the user the submission was processed |
| 43 | +successfully. |
| 44 | + |
| 45 | +form: |
| 46 | +```java |
| 47 | +/** |
| 48 | + * The form submitted by the user, part of the presentation layer, |
| 49 | + * linked to the domain layer through a data transfer object and |
| 50 | + * linked to the service layer directly. |
| 51 | + */ |
| 52 | +@Slf4j |
| 53 | +public class RegisterWorkerForm { |
| 54 | + String name; |
| 55 | + String occupation; |
| 56 | + LocalDate dateOfBirth; |
| 57 | + RegisterWorkerDto worker; |
| 58 | + /** |
| 59 | + * Service super type which the form uses as part of its service layer. |
| 60 | + */ |
| 61 | + RegisterWorkerService service = new RegisterWorkerService(); |
| 62 | + |
| 63 | + /** |
| 64 | + * Creates the form. |
| 65 | + * |
| 66 | + * @param name name of worker |
| 67 | + * @param occupation occupation of the worker |
| 68 | + * @param dateOfBirth date of birth of the worker |
| 69 | + */ |
| 70 | + public RegisterWorkerForm(String name, String occupation, LocalDate dateOfBirth) { |
| 71 | + this.name = name; |
| 72 | + this.occupation = occupation; |
| 73 | + this.dateOfBirth = dateOfBirth; |
| 74 | + } |
| 75 | + |
| 76 | + /** |
| 77 | + * Attempts to submit the form for registering a worker. |
| 78 | + */ |
| 79 | + public void submit() { |
| 80 | + //Save worker information (like name, occupation, dob) to our transfer object to be communicated between layers |
| 81 | + saveToWorker(); |
| 82 | + //call the service layer to register our worker |
| 83 | + service.registerWorker(worker); |
| 84 | + |
| 85 | + //check for any errors |
| 86 | + if (worker.getNotification().hasErrors()) { |
| 87 | + indicateErrors(); //displays errors to users |
| 88 | + LOGGER.info("Not registered, see errors"); |
| 89 | + } else { |
| 90 | + LOGGER.info("Registration Succeeded"); |
| 91 | + } |
| 92 | + } |
| 93 | + |
| 94 | + ... |
| 95 | +} |
| 96 | +``` |
| 97 | + |
| 98 | +The data transfer object (DTO) created stores the information submitted (name, occupation, date of birth), as well as |
| 99 | +information on the notification after this data has been validated (stored in the DTO class it extends). |
| 100 | +This acts as the link between the service layer and our domain layer which runs the internal logic. |
| 101 | +It also holds information on the error types created. |
| 102 | + |
| 103 | +DTO: |
| 104 | +```java |
| 105 | +/** |
| 106 | + * Data transfer object which stores information about the worker. This is carried between |
| 107 | + * objects and layers to reduce the number of method calls made. |
| 108 | + */ |
| 109 | +@Getter |
| 110 | +@Setter |
| 111 | +public class RegisterWorkerDto extends DataTransferObject { |
| 112 | + private String name; |
| 113 | + private String occupation; |
| 114 | + private LocalDate dateOfBirth; |
| 115 | + |
| 116 | + /** |
| 117 | + * Error for when name field is blank or missing. |
| 118 | + */ |
| 119 | + public static final NotificationError MISSING_NAME = |
| 120 | + new NotificationError(1, "Name is missing"); |
| 121 | + |
| 122 | + /** |
| 123 | + * Error for when occupation field is blank or missing. |
| 124 | + */ |
| 125 | + public static final NotificationError MISSING_OCCUPATION = |
| 126 | + new NotificationError(2, "Occupation is missing"); |
| 127 | + |
| 128 | + /** |
| 129 | + * Error for when date of birth field is blank or missing. |
| 130 | + */ |
| 131 | + public static final NotificationError MISSING_DOB = |
| 132 | + new NotificationError(3, "Date of birth is missing"); |
| 133 | + |
| 134 | + /** |
| 135 | + * Error for when date of birth is less than 18 years ago. |
| 136 | + */ |
| 137 | + public static final NotificationError DOB_TOO_SOON = |
| 138 | + new NotificationError(4, "Worker registered must be over 18"); |
| 139 | + |
| 140 | + |
| 141 | + protected RegisterWorkerDto() { |
| 142 | + super(); |
| 143 | + } |
| 144 | + |
| 145 | + ... |
| 146 | +} |
| 147 | +``` |
| 148 | + |
| 149 | +These errors are stored within a simple wrapper class called NotificationError. |
| 150 | + |
| 151 | +Our service layer (RegisterWorkerService) represents the framework of our service layer. Currently, it will |
| 152 | +run the commands necessary to validate our Java object without handling any of the internal logic itself, |
| 153 | +passing on the work, along with our DTO, to the domain layer. |
| 154 | + |
| 155 | +This validation itself is done in RegisterWorker which works within our domain layer. ServerCommand acts as |
| 156 | +a SuperType here for the domain and holds any DTOs needed. If it passes validation, our worker is then added into |
| 157 | +the database as submission was successful! |
| 158 | + |
| 159 | +validation: |
| 160 | +```java |
| 161 | +/** |
| 162 | + * Handles internal logic and validation for worker registration. |
| 163 | + * Part of the domain layer which collects information and sends it back to the presentation. |
| 164 | + */ |
| 165 | +@Slf4j |
| 166 | +public class RegisterWorker extends ServerCommand { |
| 167 | + protected RegisterWorker(RegisterWorkerDto worker) { |
| 168 | + super(worker); |
| 169 | + } |
| 170 | + |
| 171 | + /** |
| 172 | + * Validates the data provided and adds it to the database in the backend. |
| 173 | + */ |
| 174 | + public void run() { |
| 175 | + //make sure the information submitted is valid |
| 176 | + validate(); |
| 177 | + if (!super.getNotification().hasErrors()) { |
| 178 | + //Add worker to system in backend (not implemented here) |
| 179 | + LOGGER.info("Register worker in backend system"); |
| 180 | + } |
| 181 | + } |
| 182 | + |
| 183 | + /** |
| 184 | + * Validates our data. Checks for any errors and if found, stores them in our notification. |
| 185 | + */ |
| 186 | + private void validate() { |
| 187 | + var ourData = ((RegisterWorkerDto) this.data); |
| 188 | + //check if any of submitted data is not given |
| 189 | + failIfNullOrBlank(ourData.getName(), RegisterWorkerDto.MISSING_NAME); |
| 190 | + failIfNullOrBlank(ourData.getOccupation(), RegisterWorkerDto.MISSING_OCCUPATION); |
| 191 | + failIfNullOrBlank(ourData.getDateOfBirth().toString(), RegisterWorkerDto.MISSING_DOB); |
| 192 | + //only if DOB is not blank, then check if worker is over 18 to register. |
| 193 | + if (!super.getNotification().getErrors().contains(RegisterWorkerDto.MISSING_DOB)) { |
| 194 | + var dateOfBirth = ourData.getDateOfBirth(); |
| 195 | + var current = now().minusYears(18); |
| 196 | + fail(dateOfBirth.compareTo(current) > 0, RegisterWorkerDto.DOB_TOO_SOON); |
| 197 | + } |
| 198 | + } |
| 199 | + |
| 200 | + ... |
| 201 | +} |
| 202 | +``` |
| 203 | + |
| 204 | +After all of this explanation, we can then simulate the following inputs into the form and submit them: |
| 205 | + |
| 206 | +input: |
| 207 | +```java |
| 208 | + /** |
| 209 | + * Variables to be submitted in the form. |
| 210 | + */ |
| 211 | + private static final String NAME = ""; |
| 212 | + private static final String OCCUPATION = ""; |
| 213 | + private static final LocalDate DATE_OF_BIRTH = LocalDate.of(2016, 7, 13); |
| 214 | + |
| 215 | + RegisterWorkerForm form = new RegisterWorkerForm(NAME, OCCUPATION, DATE_OF_BIRTH); |
| 216 | + form.submit(); |
| 217 | +``` |
| 218 | + |
| 219 | +The form then processes the submission and returns these error messages to the user, showing our notification worked. |
| 220 | + |
| 221 | +output: |
| 222 | +```java |
| 223 | +18:10:00.075 [main] INFO com.iluwater.RegisterWorkerForm - Error 1: Name is missing: "" |
| 224 | +18:10:00.079 [main] INFO com.iluwater.RegisterWorkerForm - Error 2: Occupation is missing: "" |
| 225 | +18:10:00.079 [main] INFO com.iluwater.RegisterWorkerForm - Error 4: Worker registered must be over 18: "2016-07-13" |
| 226 | +18:10:00.080 [main] INFO com.iluwater.RegisterWorkerForm - Not registered, see errors |
| 227 | +``` |
| 228 | + |
| 229 | +## Class diagram |
| 230 | + |
| 231 | + |
| 232 | + |
| 233 | +## Applicability |
| 234 | + |
| 235 | +Use the notification pattern when: |
| 236 | + |
| 237 | +* You wish to communicate information about errors between the domain layer and the presentation layer. This is most applicable when a seperated presentation pattern is being used as this does not allow for direct communication between the domain and presentation. |
| 238 | + |
| 239 | +## Related patterns |
| 240 | + |
| 241 | +* [Service Layer](https://java-design-patterns.com/patterns/service-layer/) |
| 242 | +* [Data Transfer Object](https://java-design-patterns.com/patterns/data-transfer-object/) |
| 243 | +* [Domain Model](https://java-design-patterns.com/patterns/domain-model/) |
| 244 | +* [Remote Facade](https://martinfowler.com/eaaCatalog/remoteFacade.html) |
| 245 | +* [Autonomous View](https://martinfowler.com/eaaDev/AutonomousView.html) |
| 246 | +* [Layer Supertype](https://martinfowler.com/eaaCatalog/layerSupertype.html) |
| 247 | +* [Separated Presentation](https://java-design-patterns.com/patterns/data-transfer-object/) |
| 248 | + |
| 249 | +## Credits |
| 250 | + |
| 251 | +* [Martin Fowler - Notification Pattern](https://martinfowler.com/eaaDev/Notification.html) |
0 commit comments