This commit is contained in:
2024-12-07 21:07:38 +01:00
parent 2fded76a5c
commit a9676272f2
120 changed files with 15925 additions and 1 deletions

View File

@ -0,0 +1,39 @@
---
type: practical
---
## Lombok
### `@AllArgsConstructor`(onConstructor_=@Annotation)
- Creates a public constructor which takes in all args
- `onConstructor_`: Places an annotation on the constructor
### `@Data`
A shortcut for `@ToString`, `@EqualsAndHashCode`, `@Getter` on all fields, and `@Setter` on all non-final fields
## Spring
### `RestController`
Extends Controller. Used to declare a class as a REST controller
### `@Autowired`
- Annotates a constructor
- Basically, it figures out dependency injection on its own
### `@[Get/Post/Put/Delete]Mapping("/path/")`
Makes an endpoint at path.
### `@RequestParam`
Describe the param and type
```java
@GetMapping("/api/foos")
@ResponseBody
public String getFoos(@RequestParam String id) {
return "ID: " + id;
}
```
### Beans
![[Beans.canvas|Beans]]

View File

@ -0,0 +1,75 @@
---
type: practical
---
### Point: Trying to avoid technical debt
In software development and other information technology fields, technical debt (also known as design debt or code debt) is the implied cost of future reworking because a solution prioritizes expedience over long-term design.
## Assignment explanation
![[assignment_app.canvas|assignment_app]]
![[assignment_organization.canvas|assignment_organization]]
So essentially, each team specifies a module, then they exchange the specifications and start working. Then, each team PRs into each other's repo and we get a working thing.
## Diagramming
Diagramming *is very important* when it comes to communication. Doesn't seem like they ***need*** us to use UML, but I'm gonna learn it anyway.
### C4 model
C4 stands for Context, Containers, Components, and Code.
1. **Context Diagram:**
- Shows the system being modeled and its interactions with external entities such as users, systems, or services.
- Provides a high-level overview of the system's purpose and relationships with the outside world.
2. **Container Diagram**:
- Breaks down the system into high-level containers (e.g., web applications, databases, microservices).
- Shows how these containers interact with each other and with external systems.
- Defines technologies used within each container.
3. **Component Diagram**:
- Focuses on the components within a container and their relationships.
- Components represent significant building blocks like classes, libraries, or microservices.
4. **Code** (***UML***):
- Provides a detailed view at the code level, such as classes, methods, and relationships.
### Key UML Diagram Types
1. **Structural Diagrams**: Describe the static structure of a system.
- **Class Diagram**: Shows classes, their attributes, methods, and relationships among objects. Fundamental to object-oriented design.
- **Object Diagram**: Represents instances of classes (objects) at a particular moment.
- **Component Diagram**: Illustrates the organization and dependencies among software components (e.g., libraries).
- **Deployment Diagram**: Depicts the physical deployment of artifacts on nodes like servers or devices.
2. **Behavioral Diagrams**: Capture the dynamic behavior of a system.
- **Use Case Diagram**: Visualizes functional requirements and interactions between users (actors) and the system.
- **Sequence Diagram**: Shows object interactions arranged in a time sequence.
- **Activity Diagram**: Represents workflows of stepwise activities and actions.
- **State Machine Diagram**: Depicts states and transitions of an object throughout its lifecycle.
3. **Interaction Diagrams**: Focus on object interactions.
- **Communication Diagram**: Focuses on the communication between objects and the associations between them.
- **Timing Diagram**: Represents timing constraints on object interactions.
#### Relationships
- **Association**: Related but **independent**.
- **Aggregation**: One contains the other, but the **parts can exist independently**.
- **Composition**: Parts **cannot exist** without the whole.
- **Dependency**: Self-explanatory
- **Realization**: `implements`
- **Generalization**: `extends`
#### Key Components of a Sequence Diagram
1. **Objects/Participants**: These are the entities involved in the interaction, such as classes, components, or subsystems.
2. **Lifelines**: A lifeline is a dashed vertical line that represents the passage of time.
3. **Activation Bars**: Represent duration an object is active while processing messages
4. **Messages**: Arrows between lifelines that show communication between objects. Messages can be of different types:
- **Synchronous Messages**: Represented by a solid line with a filled arrowhead, indicating a call that waits for a return.
- **Asynchronous Messages**: Represented by a solid line with an open arrowhead, indicating a call that doesn't wait for a response.
- **Return Messages**: Dashed arrows that show the return of control or data after a message is processed.
5. **Loops and conditions**: Control flow
![[sequence_diagram.png]]

View File

@ -0,0 +1,189 @@
---
type: practical
---
## IoC Definition
Inversion of Control is a design principle in which custom code is executed by an external code. The control of parts of your code is delegated to another logical unit.
>Think of when you submit assignment to Themis.
You did not write the tests in the program, but they still get executed against your program, as long as the input and output processing is correct.
## Dependency Injection
It is a technique where an object receives (or is "injected" with) other objects that it depends on, rather than creating those objects itself.
### Example
<u>CoffeeBean.java</u>
```java
public class CoffeeBean {
private String type;
public CoffeeBean(String type) {
this.type = type;
}
public String getType() {
return type;
}
}
```
<u>Water.java</u>
```java
public class Water {
private int amount;
public Water(int amount) {
this.amount = amount;
}
public int getAmount() {
return amount;
}
}
```
<u>CoffeeMachine.java</u>
```java
public class CoffeeMachine {
private CoffeeBean coffeeBean; // <--------
private Water water; // Dependencies
public CoffeeMachine(CoffeeBean coffeeBean, Water water) {
this.coffeeBean = coffeeBean;
this.water = water;
}
public void makeCoffee() {
if (coffeeBean == null || water == null) {
System.out.println("He-Hell nah.");
return;
}
System.out.println("Making coffee with " + coffeeBean.getType() + " beans and " + water.getAmount() + "ml of water.");
}
}
```
And then when we instantiate `CoffeeMachine`, we **pass instances** of `Water` and `coffeeBean`.
### How Does Spring Use DI?
Spring manages the **creation** and **wiring** of objects for you. Heres how it works:
1. **Beans**: Objects managed by Spring.
2. **Spring Container**: Factory which creates beans. You always need the container.
### `@Container` vs `@Bean` vs `@Component`
- A `Component` is a `Container` all of which's methods are `Beans`
- `Bean` is a Method managed by Spring
- `Container` is the class decorator which tells spring that we have beans inside
## Spring Basics
![Video](https://www.youtube.com/watch?v=LSEYdU8Dp9Y)
Spring is a **framework**, hence *its* design has to be followed.
### Quickstart
1. Import (mvn)
2. Add `@SpringBootApplication`, import and `SpringApplication.run(Main.class)`
#### Handling a REST client
Example:
```java
// Blah blah imports blah
@Controller
public class Control {
@GetMapping("/api/path_to_resource/{dynamicValue}") // <- GET
public String lmao(@PathVariable String value) {
return "You gave me " + value;
}
@PostMapping("/api/setLol") // <- POST
public String lmao(@RequestBody String value) {
System.out.println("Received: " + value);
}
// We can also parse json n stuff
@PostMapping("/api/yoMama")
public ResponseEntity<String> parseJson(@RequestBody yoMama obj) {
String response = "Received object: name = " + obj.getName() +
", number = " + obj.getNumber() +
", hot = " + obj.isHot();
System.out.println(response);
return ResponseEntity.ok(response);
}
}
}
class yoMama {
@Getter @Setter
private String name;
@Getter @Setter
private int number;
@Getter @Setter
private boolean hot;
public yoMama() {
}
}
```
## Beans
> beans can only be uses by beans
![[Beans.canvas|Beans]]
### Definition
- Any class that is a dependency and/or is dependent
### Purpose
- Solve the problem of dependencies by traversing the project's classes and instantiating them topologically in a procedural manner
### Annotations
Check out the [[Annotation Repository]].
## Components
### Entity
- Table in a DB
- Annotated with `@Entity`
- Instance corresponds to a row in the DB table
### DTO (Data Transfer Object)
- Java object used to transfer data b/n layers
- Decouple internal data from data exposed to client
- Contain only the necessary fields
### Repository
- Encapsulates storage, retrieval and search from a DB
- Usually extended from `CrudRepository` or `JpaRepository`
- Provides CRUD (Create, Read, Update and Delete) operations and query methods for an entity
### Controller
- Handles HTTP requests and returns responses
- Maps HTTP requests to specific methods
- Usually annotated with `@RestController` and `@RequestMapping`
### Service
- Class that contains logic and operations
- Intermediary between [Controller](IoC%20and%20Spring.md#Controller) and [Repository](IoC%20and%20Spring.md#Repository)
- Annotated with `@Service`

View File

@ -0,0 +1,53 @@
---
type: practical
---
## Pyramid
![[Testing Pyramid.png]]
## Negative results
- **Error**: Mistake made by a human
- **Fault**: A manifestation of an error in software
- **Bug and Defect** are basically the same as Fault
- **Failure**: Inability of a system or component to perform its required function
## Types of tests
### Functional testing
| Fancy Words | Simpler Explanation | Stupid Dumb Word |
| ------------------- | -------------------------------------------------------------------------------------------------------------------- | ---------------------- |
| Unit Testing | Testing individual components or pieces of code in isolation. | Piece by piece testing |
| Integration Testing | Testing the interaction between integrated modules or components to ensure they work together as expected. | Mix and match testing |
| System Testing | Testing the entire system as a whole to verify it meets specified requirements. | Whole thing testing |
| Acceptance Testing | Testing to determine whether the system meets the business needs and requirements, often performed by the end-users. | Ready or not |
| Regression Testing | Re-running tests to ensure that new code changes havent broken existing functionality. | Did we break it again? |
| Sanity Testing | A quick round of testing to check if a specific function or bug fix works as intended. | Quick check testing |
| Smoke Testing | A basic check to ensure the softwares core functionality works and it's stable enough for more in-depth testing. | Is it on fire? |
| Usability Testing | Testing how user-friendly and intuitive the software is for the end-user. | Easy of use testing |
### Non-functional testing
| Fancy Words | Simpler Explanation | Stupid Dumb Word |
| --------------------- | ----------------------------------------------------------------------------------------- | -------------------------- |
| Performance Testing | Testing how well the software performs under different conditions. | How fast does it run? |
| Load Testing | Testing how the software behaves under expected user load. | How much can it carry? |
| Stress Testing | Testing the limits of the software by pushing it beyond normal operational capacity. | When will it break? |
| Volume Testing | Testing the software with a large amount of data to see if it handles it well. | How much data can it take? |
| Scalability Testing | Testing how well the software scales when the user load or data volume increases. | Can it grow? |
| Recovery Testing | Testing the software's ability to recover after a failure or crash. | Can it unfuck itself? |
| Compatibility Testing | Testing how well the software works across different environments, platforms, or devices. | Does it work with others? |
| Security Testing | Testing how well the software protects against unauthorized access and vulnerabilities. | Can it be broken into? |
## Black box vs White box
| Black box | White box |
| --------------------------------- | ---------------------- |
| Done by tester | Done by devs |
| Internal code unknown | Knowledge is required |
| Functional testing | Structure testing |
| No programming skills necessary | bruh |
| Do it break, do it work, is nice? | Do it cover all cases? |

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

View File

@ -0,0 +1,18 @@
{
"nodes":[
{"id":"3fc2b54b9eadc6ba","x":239,"y":-360,"width":501,"height":357,"color":"6","type":"group","label":"Server"},
{"id":"bafe15ec55f029a6","x":-180,"y":-360,"width":602,"height":140,"color":"5","type":"group","label":"Client-side"},
{"id":"8f80fb123ea6ce09","type":"text","text":"REST Client\n(e.g. `requests` )","x":47,"y":-320,"width":192,"height":60},
{"id":"3cfeacd18cd26023","type":"text","text":"User","x":-140,"y":-304,"width":100,"height":29},
{"id":"aa15bb82396562f8","type":"text","text":"Book Review API","x":414,"y":-320,"width":191,"height":60},
{"id":"e14bb42f26a15f31","x":260,"y":-140,"width":214,"height":117,"type":"text","text":"API Service (spring)\nExpose endpoints to manage books and reviews."},
{"id":"ac364961ecf80527","type":"text","text":"Database (MySQL)\n\nStore resources","x":509,"y":-140,"width":214,"height":117}
],
"edges":[
{"id":"902c39262a6ace6a","fromNode":"3cfeacd18cd26023","fromSide":"right","toNode":"8f80fb123ea6ce09","toSide":"left"},
{"id":"6ac86a9e5be59149","fromNode":"8f80fb123ea6ce09","fromSide":"right","toNode":"aa15bb82396562f8","toSide":"left"},
{"id":"7913e3356f9e0f7d","fromNode":"e14bb42f26a15f31","fromSide":"top","toNode":"aa15bb82396562f8","toSide":"bottom"},
{"id":"215231208aa4fd5a","fromNode":"ac364961ecf80527","fromSide":"top","toNode":"aa15bb82396562f8","toSide":"bottom"},
{"id":"f2b912e7b34b6df2","fromNode":"e14bb42f26a15f31","fromSide":"right","toNode":"ac364961ecf80527","toSide":"left"}
]
}

View File

@ -0,0 +1,19 @@
{
"nodes":[
{"id":"96f9d1aa2dfe6e72","type":"file","file":"Advanced Programming/assets/assignment/assignment_app.canvas","x":-120,"y":-340,"width":400,"height":400},
{"id":"aaa14d4a63d50f5b","type":"text","text":"Book Management Module","x":-144,"y":222,"width":206,"height":87},
{"id":"e8482c8d0391157a","type":"text","text":"Group 1","x":-205,"y":406,"width":123,"height":60,"color":"4"},
{"id":"8ae0f3cd568d7382","type":"text","text":"Review Management Mode","x":200,"y":222,"width":211,"height":87},
{"id":"43a3e65e358e014b","type":"text","text":"Group 1","x":139,"y":406,"width":123,"height":60,"color":"4"},
{"id":"7eec19f08b2193b6","type":"text","text":"Group 2","x":349,"y":406,"width":125,"height":60,"color":"1"},
{"id":"beea6cec3cb68314","type":"text","text":"Group 2","x":0,"y":406,"width":125,"height":60,"color":"1"}
],
"edges":[
{"id":"71a504bf559dfe18","fromNode":"96f9d1aa2dfe6e72","fromSide":"bottom","toNode":"aaa14d4a63d50f5b","toSide":"top"},
{"id":"d39fe30e3ea5696a","fromNode":"96f9d1aa2dfe6e72","fromSide":"bottom","toNode":"8ae0f3cd568d7382","toSide":"top"},
{"id":"8edc4e274f6adbb7","fromNode":"aaa14d4a63d50f5b","fromSide":"bottom","toNode":"e8482c8d0391157a","toSide":"top","label":"Specify"},
{"id":"c93e390557a144b8","fromNode":"aaa14d4a63d50f5b","fromSide":"bottom","toNode":"beea6cec3cb68314","toSide":"top","label":"Develop"},
{"id":"a4dc36f27812fb63","fromNode":"8ae0f3cd568d7382","fromSide":"bottom","toNode":"43a3e65e358e014b","toSide":"top","label":"Develop"},
{"id":"32534b3546e962c4","fromNode":"8ae0f3cd568d7382","fromSide":"bottom","toNode":"7eec19f08b2193b6","toSide":"top","label":"Specify"}
]
}

View File

@ -0,0 +1,15 @@
{
"nodes":[
{"id":"e19bd648197c7e91","type":"text","text":"@Component","x":-40,"y":-200,"width":163,"height":60,"color":"1"},
{"id":"c629af864eb7638e","type":"text","text":"@Service","x":-341,"y":-20,"width":250,"height":60,"color":"3"},
{"id":"04cbffca8594824f","type":"text","text":"@Controller","x":-45,"y":-20,"width":173,"height":60,"color":"5"},
{"id":"2b772d03013468ca","type":"text","text":"@Repository","x":195,"y":-20,"width":250,"height":60,"color":"6"},
{"id":"d611968995b82f13","type":"text","text":"@RestController","x":-83,"y":140,"width":250,"height":60,"color":"5"}
],
"edges":[
{"id":"dff0e847a79aaed0","fromNode":"e19bd648197c7e91","fromSide":"bottom","toNode":"c629af864eb7638e","toSide":"top"},
{"id":"d79330e26a43a6ec","fromNode":"e19bd648197c7e91","fromSide":"bottom","toNode":"04cbffca8594824f","toSide":"top"},
{"id":"c9e60f81cb276612","fromNode":"04cbffca8594824f","fromSide":"bottom","toNode":"d611968995b82f13","toSide":"top"},
{"id":"435c4bfbc0474449","fromNode":"e19bd648197c7e91","fromSide":"bottom","toNode":"2b772d03013468ca","toSide":"top"}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

View File

@ -0,0 +1,162 @@
---
type: practical
---
- [Updating information](#Updating%20information)
- [The existence of the `PATCH` request](#The%20existence%20of%20the%20%60PATCH%60%20request)
- [Usage of `PUT`](#Usage%20of%20%60PUT%60)
- [Complete request example](#Complete%20request%20example)
- [Incomplete request example](#Incomplete%20request%20example)
- [Usage of `POST`](#Usage%20of%20%60POST%60)
- [General "style" remarks](#General%20%22style%22%20remarks)
- [Conclusion / TL;DR](#Conclusion%20/%20TL;DR)
## Updating information
Can we have optional fields in the update `PUT` and `POST` request?
### The existence of the `PATCH` request
>The **`PATCH`** HTTP method applies partial modifications to a resource.
>`PATCH` is somewhat analogous to the "update" concept found in [CRUD](https://developer.mozilla.org/en-US/docs/Glossary/CRUD) (in general, HTTP is different than [CRUD](https://developer.mozilla.org/en-US/docs/Glossary/CRUD), and the two should not be confused).[^1]
Hence, this definition also answers the question as to why we **should be careful with the usage of `PATCH`** if we consider it.
### Usage of `PUT`
>The PUT method requests that the state of the target resource be
created or replaced with the state **defined by the representation**
**enclosed in the request message payload**. [^3]
This implies that complete resource representation **is required** (all the fields), as Jackson(Spring) will reset the missing ones to their default values (e.g. int = 0, boolean = false, String = null, etc.)
#### Complete request example
*Request:*
```json
{
"isbn": "978-3-16-148410-0",
"name": "Among us story",
"author": "Yo mama",
"genre": "Horror",
"publisher": "Team 22",
"publishDate": "2023-09-01",
"pages": 320
}
```
*Response:*
```json
<<< 200 OK
{
"isbn": "978-3-16-148410-0",
"name": "Among us story",
"author": "Yo mama",
"genre": "Horror",
"publisher": "Team 22",
"publishDate": "2023-09-01",
"pages": 320
}
```
#### Incomplete request example
(this is assuming the above request happened already)
*Request:*
```json
{
"isbn": "978-3-16-148410-0",
"name": "Among us story",
"publisher": "Team 22",
"pages": 320
}
```
*Response*:
```json
<<< 200 OK
{
"isbn": "978-3-16-148410-0",
"name": "Among us story",
"author": null,
"genre": null,
"publisher": "Team 22",
"publishDate": null,
"pages": 320
}
```
### Usage of `POST`
Contrary to `PUT`:
>The `POST` method is **used to request that the target resource process the enclosed representation according to the resource's own specific semantics**. The meaning of a `POST` request is determined by the server and is usually dependent on the resource identified by the Request-URI.[^4]
Therefore we *can* send requests which contain data that are non-complete resource representations. We do have to explain this to the other team tho...
i.e.
*Request:*
```json
{
"isbn": "978-3-16-148410-0"
}
```
*Response:*
``` json
<<< 201 Created
{
"isbn": "978-3-16-148410-0",
"name": null,
"author": null,
"genre": null,
"publisher": null,
"publishDate": null,
"pages": 0
}
```
## General "style" remarks
(based on the API conventions + CRUD principle)
>The base URL should be neat, elegant, and simple so that developers using your product can easily use them in their web applications. A long and difficult-to-read base URL is not just bad to look at, but can also be prone to mistakes when trying to recode it. Nouns should always be trusted.[^2]
Hence, using a verb in `POST /api/v1/book/create` and `PUT /api/v1/book/update` is not ideal.
Table[^2] of common conventions for the usage of HTTP requests:
| **Resource** | `POST` | `GET` | `PUT` | `DELETE` |
| ------------------- | --------------------------------- | ----------------------------------- | --------------------------------------------- | -------------------------------- |
| /customers | Create a new customer | Retrieve all customers | Bulk update of customers | Remove all customers |
| /customers/1 | Error | Retrieve the details for customer 1 | Update the details of customer 1 if it exists | Remove customer 1 |
| /customers/1/orders | Create a new order for customer 1 | Retrieve all orders for customer 1 | Bulk update of orders for customer 1 | Remove all orders for customer 1 |
Hence, in our case:
| **Resource** | `POST` | `GET` | `PUT` | `DELETE` |
| ------------- | ----------------- | -------------------------------------------------------- | ------------------------------------------------ | ----------------------- |
| /books | Create a new book | Retrieve all books (+ optional parameters for filtering) | 405 | Remove all books |
| /books/{isbn} | 405 | Retrieve the details for book with `isbn` | Update the details of book with `isbn` if exists | Remove book with `isbn` |
## Conclusion / TL;DR
Given the information provided above, I propose we change the current specification as follows:
| Method | Old URL/Description | New URL/Description | Reasoning |
| -------- | ---------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `POST` | `/api/v1/book/create` - Create book - pass JSON body (isbn is required) | `/api/v1/books` - Create a book - pass JSON body (isbn is required) | Use resource-based naming convention (`books` as a collection). HTTP POST implies creation; no need for `/create`. |
| `PUT` | `/api/v1/book/update` - Update book - pass JSON body (isbn is required) | `/api/v1/books/{isbn}` - Update a specific book - pass JSON body | Use path parameter (`{isbn}`) to specify which book to update. HTTP PUT implies updating a specific resource. |
| `GET` | `/api/v1/book?genre={genre}&author={author}&data="csv"` - Get book(s) by property in csv/json format | `/api/v1/books?genre={genre}&author={author}` - Get books by property. Use `Accept` header for csv/json format | Use plural `books` for collections. Use HTTP `Accept` header for content negotiation (CSV/JSON), keeping URLs clean. |
| `DELETE` | `/api/v1/book?genre={genre}&author={author}` - Delete book(s) by property | `/api/v1/books` - Delete books - pass filter criteria in body or delete one by `/api/v1/books/{isbn}` | `DELETE` requests with body for filtering criteria, or use path param for deleting a single resource for better clarity. |
| `POST` | `/api/v1/book/import?data="csv"` - Import books from csv/json file (isbn is required for each) | `/api/v1/books/import` - Import books from CSV/JSON file - pass file in body; use `Content-Type` header | Use resource-based naming (`books/import`). The `Content-Type` header indicates file type (CSV/JSON); keep query params clean. |
[^1]: [Mozilla](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH)
[^2]: [Swagger](https://swagger.io/resources/articles/best-practices-in-api-design/)
[^3]: [RFC 7231 - PUT](https://datatracker.ietf.org/doc/html/rfc7231#autoid-37)
[^4]: [RFC 7231 - POST](https://datatracker.ietf.org/doc/html/rfc7231#autoid-36)

View File

@ -0,0 +1,43 @@
---
type: practical
---
## Genre
- Cool, nice. We will extend.
## FileService
- `importBooks` doesn't actually save books
- CSV parsing might fail if there are commas in the data itself
- Hardcoded CSV column names (just an observation, not necessarily bad)
- File creation creates temp files without deleting them
## BookService
- `createBook` doesn't ensure isbn uniqueness
## BookRepository
- Our documentation is not updated. The class map cointains findByISBN.
- Only 'isbn' is supported. Implement:
- findByAttributes(BookAttributes attrs)
- findByID(long pk)
- deleteByAttributes
- deleteByID
## BookAttributes
- Every field should be private (@Data automatically adds gsetters)
- Perhaps add data validation? Not necessary.
## Book
- Attributes can be null. Add null checks in getters and setters to avoid. We don't want null ptr exceptions
## BookController
- Do not interact with repository directly. Go through service.
## BookService
- `createBook` returns true always, no matter what the status is
- matchingAny is OR, false positives might be returned. Perhaps use matchingAll? Not sure about this.
- `deleteBooksByAttribute` is missing

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,101 @@
---
excalidraw-plugin: parsed
tags:
- excalidraw
type: practical
---
==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== You can decompress Drawing data with the command palette: 'Decompress current Excalidraw file'. For more info check in plugin settings under 'Saving'
# Excalidraw Data
## Text Elements
%%
## Drawing
```compressed-json
N4KAkARALgngDgUwgLgAQQQDwMYEMA2AlgCYBOuA7hADTgQBuCpAzoQPYB2KqATLZMzYBXUtiRoIACyhQ4zZAHoFAc0JRJQgEYA6bGwC2CgF7N6hbEcK4OCtptbErHALRY8RMpWdx8Q1TdIEfARcZgRmBShcZQUebQAWbQBGGjoghH0EDihmbgBtcDBQMBKIEm4IAFFJYgAOeIB5ChyAZQpmOAB9AA0AaQBWAClh5VSSyFhECoAzQIRPKn5SzG5n
eKSABm0efqXIGFWAZiSANm0Adn7z2qSdvYgKEnVueNr7yQRCZWluJP6N+7WZTBbgAwoCKCkNgAawQAGE2Pg2KQKgBiJIIDEYsalTS4bDQ5RQoQcYgIpEoiSQ6wdXCBbI4yDTQj4fAtWAgiSCDyMiDMSEwhAAdSekm4fHBfIFsPZME56G55XuxO+HHCuTQSXubDg+LUB01GzB4wgROEcAAksQNag8gBde6zXCZK3cDhCVn3QikrAVXAbXnE0lq5g2
92eyVhebio0bQ79HhJc7ne6MFjsLhoeIATlTTFYnAAcpwxL9DonDvUNtndpLCMwACLpKDRtCQoQIe6aYSkyrBTLZMMe/D3IRwYi4FvEX7nQ4bV48bMnE79WopyVEDjQt3D+5Igmt1DTAhhe5wNjenL5cFgArjErG+8bG8Om93p97ErOJIvz/v8bfp+YDfr+b43sBWrgc+96vve/5fpB95gIcoFwVBQG1Kh4zwchQE8CcWElDhJxAZWhG3uBmxAf0
P4wX+lGHEBK7kThSZAdcLHgTwj7jNm0HjLB2FcRKSHZphdFgUhPDxEBmzxJxUkyZRGziQJ9GKbJGznAp97SZpKESWhUmMeBfE6eMPAifeNzmSUibsQZamSfebHgf0jklIJRGUbWSHrLZYC3HhqmeepulAc42YBTxCEhWAXkUUhgHgc4+EBclSWHB58VheMiH3s4ZGGUJSEmUl/nFd5SG+QV8Q8AF67lfVlWJS5MXAXVAVJGVtX8aFzl5Y1BVFU5R
m6SRKWLgF5YRdJ01DQBSYBesEWbMtNUAdlCU4fEC0lFtuUlLtGHrbJzWjSV951bJBEtThhx7YFcXbeBxyyVFd1cflg0BfZlHaTB4JeRAcCBKGIjhNeH43ls0knEktznCclY3CctRZRtYCw9mSR8f8lzHKcJxaXtWz9P08Qqf08OWdm5wbKu31Y9oVyJmJcZZeWtTSaTLPZocy6XFpSanHOuYw9oJzSdWlPVoc2bZusK0S8juMJpTOaXLUONM1syN
oyu5yKyutTLtWn565WWUbMjVam8T4tPpLlPXLOyMMzwAvcxbztJkbST1IcStzrzJx1Zcq42/E8TE5cSlO2HPAR7UUcx1pFM+8j1x1fG5x/Pb5sq+W2Y8NrJznLtq465n5bRzbpuWVXCM10kMfrJ75w7NrzdF63Ye3A9XfV0X1bE6uXto6XE0J3Ou2WXV6OJujbwj3P0ml+WAeVpni5ifL8Orkv28q0aCsB/ENFi9x8fjGT6OKxXRud689Q+1cMcU
5cT8rjNEvv0Tu1y74XclZB8LMK4AKRp3H+oDmZXHpkuSyVsEZIxXk7K4NYHruxUigtGb96jxHjNzBmRsEy1DQbfPmxxkZLjzuWamr8T6xiNJ7eoAcMGZ2rPUbi8MFZh2npQqW+Fur1E7uTemeCVY7C0iQgOCsla60lqXf4lZ+h8UIbcbqO8Fb3wPtzLeFCwHuyXAzdGBtuamxrqPUxE8LECKMbPTu69F4GO0UaWcCsVJH0MczKWlwk6LhXBoisO9
KaK1PvLRWrdFFqIRjWBMD0Ubw0UTsUhWC5w4L9hbIG9xWD6A9JOBAAAFMGzAIbcGPPgU8G5QhQARPofQagpxFIvAyNA8E/ouQBp5QoABfJYxRSjlAkPgAAmkKAAggACQALL4CFDwGADRmANG6AAGWhBwAAKp0AAQvEXkkxxDoFmAgeY5BFiShWGgQq5ZtgbQgAaVAqUEwXCuDcO4kpHjEGeGgRc7xPjfCgC8HqkAgTyh4lKKEsJyTIjRFiTESAuz
4kJEGMkiI4VUnIBwWk9JgWOhZGyDkxy+SIiVJGaUwpRTilyZS2U8pSU8mVMIVU6pfjal1NgfUvwjT3DNGOK0Np7SOnIC6acaBwwjjrD6a56BcApGZSSYgIYhwRhNFGcVvBW6RNSQ8tMBZMy8EMQwfMGZiwcFLH8uJxDNZekbM2Q87ZOySm7EqvsGQshXglbuSUY4JxThnLPbmS4TYLQgJubc3q1WlH3LCTVlTqkmnPJeIVA0HxAT6jlNNgVSK/XY
stG6AVMZgG4kWvC8lPpIXsXZbp/UxrjGrSW56h0wCNp2AFNttELpVXvI204ZblIDr8kOgqXa62XXGMW5wt1u2tUnRFD6s6cKPW/GOrN9aSiO1HQdbNPckqty6lOv4XUfEQX6L9JmEEK1Lq4jfRaO6N0lpXQjX6W6AKLvHT2iyb6vwfvijkyUoN1QQ1TdDJ2G8FxyXcmJPOsCtjUNqO5Q+iG+Ly1BczeMK4EYp1bh8mONYfYC26lvI2YcSEl0I4nJ
MGCBa7SRtW+DK5Ba43binAWmNGM0WuDjUu1w4wrkIxXLKhD4zljzq8aOhGcZGxtofeMFMFY+yVjsXGntqabEQUpkTNx5bdyltHDjCQCaJzplLKsSclMKx2PItcs80aXDfrjYNMswmK3wm/eWlN+7kKNPGO9cCKZUxpouemjM35qJMXOamfEkYZz/rw3jOYVLR07rzam0kkxxg5iliz8WMv0znMJueaXKw4x0xHOMcWnYx3wlWLDlWxaEfWGHGsGw
VMYOUT7BGAsw4XyuNh9Yq5sndr5IQfJ+BCklOA4ECpJ5nUmgm/yepjSZDzBaZeKG37PyRSBn0gZdZNWPPoEkAAivgXZcI1kACUABi0wGjZiKfQXo9BCzSADPcI5Mw5gLF5LK1Kpt7n3CeS884bzri3Aed835RqAVfB+GgeMgIODAmOZC/k0L4QYspOgdEiLsTIoJPy0ksKcfQGxbiz1vJmSsnpSSxU05aWY5FD8sUfymeCjpxUBngYWWSBVeyyUO
o9SwB5ZC/llprT5GBk6MVO5o2QG9MQX0EhcA8F50qgXUapXqrOZq04sSzPHUlPqjM4p7EmvTEWEsxyvOmzoQ8+sTZggBrbKQDsXYezEHdQOL1qBJWjnHIU/Xs55zBuXKuMNEb5c65jWwA88a5tnlaX7+COF2qZpevujN6VL1rqz6OoC17P1zq/LAsAtb10TrL2dXPslm3Zvap7PNXEG+Pv88hc6Jfl2kXPZWlyl74wntIvnltnTdKV4LxZDvzf+8
WT7W36vJa+0PqX580Sv1y8zu75RH9YBi9V6/SUU4eEurl9H9mx6q+j8V9zYDEbQHwZg02+miWlYTiYJAR/+BbWGMJBJjRGokaN1P8JTOhlsMEvGPPEaIhsTHBgkKrJ3EHMjEho1hLK8G1scOEhXO7BXEprAXGAPKhknBfP5hAdrG1gjPPOTGoh/vgTotJOTEuOQgwpZsmK1pcEmDcDHALI5tJNrJrMuH8HxH/gkrOIFpTK3JHmQSzHOMTLjNcHON
xG1h5pTMoTbExvwiVhXCAvzCnJVjIdBl5usD5gYR5mJA/LGColVpQoFpWIuDpr5jYWAnYcGo4WYX/GAW4d1E4YYV4Q4T4R4dVlZhFq8KHjHGuIZomFcOWB7CXPPOQmQQBuqmNgUi2FNk/scgmvNjGrUstk0mtini/rfjeB5HaHtoUIMorodoWJoDkIMEYBMvQAAFaSCDANhRANgtBjaEATIABahy8AJKpy5ylAf2qwns2YQOkoIO5YcQX8HyUO1K
WYoCUggKCOqAYkyOqOoIHOMK2O8K+OSKLqKKxO6KFIFQ1IOKuoeK1OhKXOXIZKjOFKzOyxvAexCADxCoTxGurKoYguJowuXKouho4uxIkuQqMuoqCAro2uXoMqfohwGuwYbKcJkYeuvwS4hWqsb6luBqLwqxpu1uFqxyCMfWtwUsju9qLujq7uORkArqvY/YnqqqsekAfqwegaYegSoae43oka/uPqC28ecas2VS9JIMKeoGS+Ge0Uq0deKUF+j6
GUuk6U5efeN6SU5ef6U+CEskGpO+SETeXeh+peOar0SpS+6G++meLaxaOYDU1Ei+N+e694lwZ+GElpLpHea4h6QEZkc+x+ja/MXUj0NYw+pk2+pprEe+dMv07US4F6/pUZupJa1piZgZJaxa4ZmZuWokB+qZ/ypk1+Zpv8okXppZPpOpLaD0GEKZNZe+9Qy07U3MzZ7E1Z2a8s7Ek+NZp67pmZZZbpBZY+fZBp0Zt67E9Z2a4+4wHEmZAcGEtpu6
p6KcL4yRpQj+ZSz+7SMMPs+EyY84Sc3Mch5ciich0cH+8sEiKcHcSm8mVmdMyiWkfCSm+sxMfixMomhc1W9QUsP8pwp8CYfwlmFiGMic3B+sb81YDMUsmwXG5CWUjmZidU/wK48i7mf8iY1wlkMmUha4hh68pWdc7kLWP6ZMOw0RqF5MtGimmFQF5MsmJFOYZFLMFFfwVFTFtF6CwiJctwpsh8D8b8PFiYU8Alxu3FCWfFJsi44lth+EaM841YfE
waslLhi4miqM+cMlaW6lS8pwWlglmF3BCYJFYlOlsWH+z8Xihl3FFlj5lMKl5lTG9l1lqlcCSczlVljlQlxlOw7G3lmFdlXl2lQlQV0sAVElqmUlZlQlVwcYoiDlIVdFlFjFNFLF6srCislMnF6VnsPMtwHlF8+FQltcsGhV0hQlOcAh2VaVjmDMAsg8QhQBf+hCNECMGwAF8Yz5S4hGXBrcOMWctMFGEsmwy4GSdUassRWiMM65AgqRE26RpS5S
aA2Re4eRBgK2zSRRO5LkPiq5AkFRJQVRZQh2NQAAjucLsucA0BMg0EYPoNMHCN1FMhaEYMQKMvoIMVMBICMb9vcP9qcLUNMSaCDpZEkODosfcNDmzqgK5CaB8PDsCmgFsZKOCmjh8aTocQiryHiETmipjVijSDcVTgSrTsStzj8R8SzjDqsRjpzuTY8UypKCqPzqibDRyiLk8psGCeaIKtLiKs6DCZqgHtKsrrKhALgAcoqiif8WibroeN1EHA/K
cGGkSYanDaUGreaparDcYgmKeXas7ggK7qgE6p7m6syYODHoHv6greEeHryRuPydbRuCKYeKtYBlKcUenjnrmRFGOamdaaugWoqc6WaR3qlFOY+kHUPvORFBfAqUlOTOlBHcxJmc4Cuv2ZqaOu1M4OjLnpepFEucqUFClDmdnfegumHThN+EHWJGuQ/ktdubaLuW/tJGJGNWNabOsLwW/vwiIdQqXPxoZrLB1frbtDmAmLzHVA4afGfGHIzOAQkA
LCpAsUht1S1QvPzLgsuNJBfFpu5KporCXK1pJugTVinGot3fOOsCxReUPVfWRtHLjEpkmDQoQiJkjMIbzBjA5QwvYfhIOWAp7FLKPLHGjP8NrD7DbLjMuOQqbLJq8G9DNSNnkmkcUk3VkUnjUkthtQUcQOtm0i3duttvXQdSUP0pUQdhUA0DwAAOL6BQDVjnB0MABq2YhYDYQg/QcIvQZ1V2mAQgX1wxP2Fy4xNyrcYO6+pQoNWUENkOUNbxlMcO
QK3AvdJoaNuxLxgoBNuOCKBOJxeNXuuj5ORNdIJNkoNORKco9OlN2jsI1NMNtNdKDN3xTNJoLNWu7NQunK3KoJfK4JfN7SUJgtsJgpCuZQCJqu/QyJyqbNIt8t+u0WoeOGeYVuhqQDeJZqNu3AS4q4Mc+lhtDqmqZtLqXuPuLJLtJoHJJtSYQaPJkefJW4VTceCeYpiaG5XtO1MpvtFdx+vThp4UUEHa8pmZp6qUXUqdoZC68Zq0JphZ5e34AdY+
QdAcv0QdiYm+s0JZOE3Es0w5u6K6c085GzOz4E4zlkcpipyz2aqUq0FZNdQdUdS+dzipidV0LeSE1d5zteJzvzfTT0/zgz0+mkr6J0A5HezzN+tZr080d+5Dn6IMmD0pr+4GQcQcl9a4Ro6mCYe51F3UiCllYkpchG1stwylllfFS98YiMRsE9XcOclGecY9/wmwSCXZb+CCoD6MWUS4Osf+g8iRncMFKMiGhmiSiSwBOm8D5MgmWUoeHV0rLB4r
s4krirIiyrcrv5bW4cC4Y8gmC4wihs4mBCBr3MRr8CuGZ9TsD0CYNYNC8VVMisgm7y8s3EpcKcKGMhlYKt8sbWScxwEOSmWkR51MEcOwEeS9hCbWq4iBNsHcPiEBMcqCfEfLhVUbH+HVK4NEdM2qNBSmRsOw7kEmhCYkI9yFfCOMgF0R+BaVH+3NNYNb6BqC6lecAcIbNESmHr0FybF8GS1L8MKcy4xMFBHWe5xC2C4hu0DVS97V1MBs/MfG2sZF
s1o242k2mD7TEpi2dSeDq2BD21xD4wML94ZRh1RQ1DEg5gCA/QbAMA2YgwQouy2YUAAAqjMvoE0bUAgCcDMsI99nrmI/9asDWMDTIxMTmPI9I5ANDdwM4WsYjWo5Cpo2gOjpSiY3jtjYTqisYwcYTdceYwyKTdYwyjzlTW8c45jl8YyuSh43zl499BAECX47DbypKBLkE7aCE3LnLUMlE3KicLE14wk6UBqr8GouHJNWk/iVmHqqasSTrUlnGPEo
x07sU9wKUyaIyd7pbX7iJ+yUHrU/bQ02uE0wKfp+Gm7YnuKcnimt7ehMM3HZRF1Kfn7W5NNP6SHUhLcF5y5FC+HXhA8+BDPmc6VHhKF6e4Fx57vtF95/57s0C+Od5+CwC5egcyXbJGqRFBF5tP7elEHT2bc4szcxlylDl/0855mR3iV0vo9N86JLJEF953nvMy2sBZRK14c4111LGUmeBPF1xNael2vsWk1+NKMwCyS5NGC2V9NKs9NG2m2eBIV+
3h3j51V6ektJmZ21xMXUvjt1JJnqu5uSBsUTFLDIhg9DPSYRJ+o5QrxqXABfhImFpIhnuSXIhgjP8JVohjcOK4QbQpHMcPUAd5QiHOQuSwwkpaZr1QjPpeQkjBfJ3K3L1YVuQtZgj9/tSz4SjBj2uFj6j/FdzFffj5gr1SlsD7AzySxccBTwHFTyGjT4DUhhYkAWhoRnvXvNReQv3Hd2AllBpm1uJp3PzN+WD0HDlhi5mxzKS3xjPUOx4inD/ZIT
omuP4roQK61cj1eZEnxJ7JRvhjbIPWxgJugXvYEvUGr/LNTFGzsF5knHnE/AuWb1XH1twrm4mwkIA6BbQh7127bBvL73nJ7+HBL3U0fewV24oYwdHHVHTEbFH2H7OBHwn2b7OM/cnyfZH2n0n1gSFqn9VkebXBTNHPn3fUXzH6X/H+X9H6QVX9n4X7XyX3Hw35QgvCnOouXH275neYG8cMoUvModPXVBrMhacHvSPVvYTPRvpvvefaXNvW/bvQZl
238H8JZPrD3e/KS2jFvX1j3d7CNTcLFjHB/qXAuDbMNoi2gwtRg9NlgzZzg7uw0vg4Q0jUeyUKD3ZH1OURQ/tiaMMnQA7AKAuAZohMmYAwA4QcATQHCAaBFIYAgwboFshOCsNRgn2IYgB1GKXITQsqfoG8mBwTErgUHJYqznFBhoEaqjLMKCgloo4IUGNPDnoyOI41Ti+NBgaYwI63FiO1HMjvYypSkD2cvA7gXYzo5+BWastbxoCV8YglWOPNAV
FLmCYC0eO4TNkpEzFp+hzgQneJkKVE4YlkaAcc1hjGk5m4/kqteThwG1qklBYZiDqkUxpIlM6S5tJkh6itq8cDOttEPPUxDSNMnazTVwZZzaYrVsGSaLph/zNKyl06l6PbjfleZGkU6AzJLgVD7TpQ985XCCOqVzx9oxui0PfMenTp7N/oapdqGswiF75S0EQx6JswiHFpLmEQ8vMcwBZ508IA3ZLhaV87HtUhV+Zob2iLxRCzSaXVITEKugDD7S
Aw09BVAaFjD88x3ZFmdwLalZFY7kNcOZmnrLtuYfWZgrAU94qI/g0PCmGPBiTdRT+NsLNsIUv5/xcYQcHBMcFCIYVuKScVcGwhohFU8CRlKQn5Wko2VbCrLI2EoTP490l6GCecPzBfjT9JECcFSERhrALCPEDmKRMTF2iHDIkCiTOGoU8Q3AbyXseAlhnfKYFCYB8TOPkyWE28fMw1BOGHAehLs4qpsZ3mSLRhBx3Im8C+PW0zhEtp2wafCFEhZE
n1rU7w6hCyJ8yttfMqsP/GRldgNUPYmIt+PRmfIoUbgb9EPna1/piQO+xlPcrvC0jlxjWUPDjKuxv4bt7+W7Nargxf77s3+xRRoTeHGH/o/+VDAAYdiFDQgWQoyZQAsmcC9AJkhYaYNmGICYAEAbASQK3H/Y/VRGYxYDmgFXD4CZiExNGMQMUb8DUAkKCgRsQeQocEx9Ai4hIEw6IpmBRjJVCYyuKU4iOlje4q4xo7PF1UlKRxjSkEGlieBIgv4j
aEY7MdpB3NAJrzXkFcdFBQtFporn44S1agmg8QRZzE6agk4/VBmHPxNBq1uApg9JhYJjAj9+WYaNTnYI04OCymFtZwXp20FuDOSmoYzl4NM4+DzOO4/waKUCGP9ghdnbpjfnCEAsihVzUqI+PvA1dbx1EBuoixO7N14I53VioQVnCXA0KbmFqrGHX5hwK4jbXmAHEsh5U+sY9aCTqNQbzV9RmRQ0U/3yKmjD2P4z8AaV/5gBKGR1S9ugFYZGAZk9
YCZDMnoBwA+imgToC0G6BnUOgPAE7NCG3DoDvqJyEMdgOWDcBlwkYkGhMTkYLEFGXyN4oxyTHv9GOqYtDpjgw76NjiWnFgbhwzHoACxxNIsSaCsZCD3GonSsRRw+LaTaOpQTxmzSbFSCuabHE0Bxw7HCpLG0JMJhZyVwq45U2YQcayVyS6DUA7+VNk2ynFmDeJRghTsckTCA0UKyYWwcbVpIe4NxTg33O5N9SGc7angiPEeIWzO0/Bsad2kEM6bX
jQhPtRzql3iGpk88LnSrgi0PxIt7+KLLGJwn5iqI2YMROmCPW96B9y4fvFBtf2QmLUDRF4jppAB3YYStqG2G8eaRci7YbRhEu0RUHwDxBnA+AOAEIAmT9AikFofoLshaDKAoAJwXoM0SFDxB8AQYziYB1DFXJVgu0WGA8hBwf5Yxok+MVRElASTxO2xOgbwLklMDsOZxfMRTnUn4pixZNGxhTR0kQhXi8YyjvTQBmM0jJkAEyeILMmc0xcbYuQZC
S7EOTTxTk8WrgAmRuSexfITyfQjGqoZAphqXFibjMHzjwx6wasAklU7UlIp9g6KVp3Ka6d4p1TRKR4O5KHio86U5QXuCs5oSrxLgvKQ51iERCJuwLPUtNzyHZd8ueXdOlMzlkRQVuLzKdErOiGNoM66UFdK+LNIZ146mssWQkIAiLd06xabWTXUhYyzzmWXK2aLP66lTh0bnbzrFwKiddo6esgcrLMm4RQuhFkT2eLNGm1R3mm0a2fuktlOyTZ74
iOXbKjlIRVZOs4tPVwKjFo455syOQ0I7wDC1uYc0dNnIAiuyXmMdXOV+GG7RCI6Jc+ObNHShHp0oSQ9OsbIaGNphhReKuWnP9nOBTZH4iqV+MhgjTfx6lQhNxDIT4w8qP9Y4NBTHFzxReNPQdhoRHZ4xC2hGU2J/nLDf4DytwtvvpR0KxxEMi4PnszFj7oxNg3UNqaARkKTzLI1Iq4COzjCOYgC/ieQlLD8weZu45WBik/M8JZQk4MGFjDAwBHRx
Z4CmD/F5mmroJe28hcmGETOGgKu+4CimFi1EI1Z4EQcBwhIjfgpYPuYsOuPDABF+ZLeoBDQsmx8pvDTKEVOSuSzQx/d0KohFSM+TDg4ZrgPLSzIRW1jusUEpItvvxVIzX0xMpBWtqRXrbVt2uP5OtlWypm+SOFB/aRF1UbZCKOFbVV4HxX+AyLy28ij5IhjEWyKwErwVRYoo0UqLPyRBOMCQWcL7yEGOMbKn8B4UmLyChsZgmRisVnyfMBmCLDQT
5b4Eas/5YmHor4XMUBF3i9AhWz5YNt6KtbfSibDDxSwYeAS6hAbhTivBIlSMUJfDHCXxKQsLVbmOXHlZtSG4US4RfwtEXKL8C1MQgpMSMU6FDCcFBqtAgPgiFb5t9PevRjUwVKWCflXGEpTqiiF9BJlLAu0vXkuEulrS1NsBMsytxp2i4ICkmHYVaL6eDvdivj2/p7k3Yv3SHh1XRZX8Kpeo7qahN6nbt1qJooaUQ3gjtycJ40/CeAFggS04AcAd
kIUm4CDJoAHwTIBUAnCkA2JhQBgIQAQAUBdkSkvMWwNRDTBAVQKnEBAGwAiA8UFoFsPoHZCyT/l8kkFWCtIAQqoVPy3MSTjYFqTCOv00oIiuRUZAbsJYiGW4yMmgrwVnqSFRkBhWCgqxAgnFWSuyAUroVLjIlWWIRX0qoAjKq7PR1MlLBSVSK8lVCoaDmSEZ7y3FQKvxWcAoAN2Z0CyCeQ1Q+VeK/QDdklXdEjAwUx8AqvFX6AtkWAKABMiIDKBD
U4aBANMGxWQAxVDKqFTctIB6qkVbACgB8FwDC1tBmqy1RkEqCkgJkdqh1SEEOx0goQ3E11RyqhVeqA1WyDARIDRQgrmA2AKEKyG6Azjrg/EgQLGsRBjI1GgOdeAHFhGlAjAbAAwHcpNwEAOwvwc9kGs5Ve4vGEAKNbyqJAkBVV6q2taQHrUtg4AcHJtSQBmRsBlcHq3AJoGCDWcOmpoZtecUxSoBjquyREIdlRBwhsws62dbyCuwIBlABSMnKiEq
ANgN1G6iAGWotVQAqVsIIVVAAzAsymQ0JJdT6GbUo5C1JoLIH2oHVriGZOKogG2rdxPrIAHAQWo+olLCAoAm4Y5E6jLV2BmiCAbAK0E/VwAu1Paz9f2qymXiwUYGwgIwC2T5qDpaAY6l9i5DpBENnAXkGCv5AGBw1HEizplMHW7L+QEyRDchtQ0x4+k4AAiRABpw9zx1vSEAL0iAA===
```
%%

View File

@ -0,0 +1,16 @@
{
"nodes":[
{"id":"0b575c52e89e5e7f","x":-868,"y":-500,"width":1018,"height":660,"color":"1","type":"group","label":"Problem"},
{"id":"59984a2333620a30","type":"text","text":"Problem - Too much reused logic\n","x":-580,"y":-480,"width":320,"height":80},
{"id":"4a3daa677e26d98e","x":-763,"y":-235,"width":250,"height":155,"type":"text","text":"`@Entity`s (Book, Review, Album) all have an `id` and an `attributes` field."},
{"id":"189837e2bb3fcc18","x":-440,"y":-233,"width":250,"height":153,"type":"text","text":"Controllers and services for each entity perform almost identical operations."},
{"id":"1144a58bdce0327e","x":-848,"y":15,"width":308,"height":125,"type":"text","text":"The `@Embeddable` attributes are also functionally the same, although with different values"},
{"id":"5739ca50af3e054a","x":-120,"y":-235,"width":250,"height":117,"type":"text","text":"Updates to common functionality require changes in multiple places"}
],
"edges":[
{"id":"2b2b795da278ff01","fromNode":"59984a2333620a30","fromSide":"bottom","toNode":"4a3daa677e26d98e","toSide":"top"},
{"id":"2ea24f6cc0e66abd","fromNode":"59984a2333620a30","fromSide":"bottom","toNode":"189837e2bb3fcc18","toSide":"top"},
{"id":"6b94289b63a6655a","fromNode":"4a3daa677e26d98e","fromSide":"bottom","toNode":"1144a58bdce0327e","toSide":"top"},
{"id":"c4ea927674cd8e4d","fromNode":"59984a2333620a30","fromSide":"bottom","toNode":"5739ca50af3e054a","toSide":"top"}
]
}