You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
In this commit, I've improved the documentation for the following design patterns:
- Collection Pipeline: Added real-world examples, plain language explanation, and Wikipedia reference. Also included a programmatic example.
- Commander: Provided real-world scenarios, plain language description, and a Wikipedia link. Added a programmatic example where applicable.
- CQRS: Enhanced documentation with real-world illustrations, a simplified explanation in plain words, and a Wikipedia reference. Programmatic examples will be added as needed.
These updates align with the acceptance criteria, ensuring that each pattern's README.md file contains a comprehensive explanation.
Copy file name to clipboardExpand all lines: collection-pipeline/README.md
+34Lines changed: 34 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -10,6 +10,40 @@ tag:
10
10
Collection Pipeline introduces Function Composition and Collection Pipeline, two functional-style patterns that you can combine to iterate collections in your code.
11
11
In functional programming, it's common to sequence complex operations through a series of smaller modular functions or operations. The series is called a composition of functions, or a function composition. When a collection of data flows through a function composition, it becomes a collection pipeline. Function Composition and Collection Pipeline are two design patterns frequently used in functional-style programming.
12
12
13
+
## Explanation
14
+
Real-world example:
15
+
16
+
Imagine you're managing a library with a vast collection of books. You need to find all the books published in the last year, sort them by author's last name, and then display their titles. This is a real-world scenario where the Collection Pipeline pattern can be applied. Each step in the process (filtering, sorting, and displaying) can be seen as a stage in the pipeline.
17
+
18
+
In plain words:
19
+
20
+
The Collection Pipeline pattern is like an assembly line for data processing. It breaks down a complex operation into a series of smaller, easier-to-handle steps. Imagine an assembly line in a car factory; each station does a specific job before passing the product to the next station. Similarly, in code, data flows through a sequence of operations, with each operation transforming or filtering it, creating a "pipeline" of data processing.
21
+
22
+
Wikipedia says:
23
+
24
+
The [Collection Pipeline pattern](https://en.wikipedia.org/wiki/Pipeline_(software)) is a software design pattern that allows you to process data in a sequence of stages. Each stage performs a specific operation on the data and passes it to the next stage. This pattern is commonly used in functional programming to create readable and maintainable code for data manipulation.
25
+
26
+
Programmatic example:
27
+
28
+
```python
29
+
# Sample list of books with title, author, and publication year
Copy file name to clipboardExpand all lines: commander/README.md
+75Lines changed: 75 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -20,6 +20,81 @@ This pattern can be used when we need to make commits into 2 (or more) databases
20
20
Handling distributed transactions can be tricky, but if we choose to not handle it carefully, there could be unwanted consequences. Say, we have an e-commerce website which has a Payment microservice and a Shipping microservice. If the shipping is available currently but payment service is not up, or vice versa, how would we deal with it after having already received the order from the user?
21
21
We need a mechanism in place which can handle these kinds of situations. We have to direct the order to either one of the services (in this example, shipping) and then add the order into the database of the other service (in this example, payment), since two databses cannot be updated atomically. If currently unable to do it, there should be a queue where this request can be queued, and there has to be a mechanism which allows for a failure in the queueing as well. All this needs to be done by constant retries while ensuring idempotence (even if the request is made several times, the change should only be applied once) by a commander class, to reach a state of eventual consistency.
22
22
23
+
### Real-world example:
24
+
25
+
Imagine you're running an e-commerce website with separate microservices for payments and shipping. When a customer places an order, you need to ensure that both the payment and shipping processes are successful. However, if one service is available and the other isn't, handling this situation can be complex. The Commander pattern comes to the rescue by directing the order to one service, then recording it in the database of the other service, even though atomic updates across both databases are not possible. It also includes a queuing mechanism for handling situations when immediate processing isn't possible. The Commander pattern, with its constant retries and idempotence, ensures eventual consistency in such scenarios.
26
+
27
+
### In plain words:
28
+
29
+
The Commander pattern helps manage distributed transactions, where multiple actions need to happen across different services or databases, but you can't guarantee they'll all happen atomically. Think of it as orchestrating a dance between services – if one is ready to perform and the other isn't, you have to guide the process and keep track of what's happening. Even if things go wrong or need to be retried, you make sure that the end result is consistent and doesn't result in double work.
30
+
31
+
### Wikipedia says:
32
+
33
+
The [Commander pattern](https://en.wikipedia.org/wiki/Commander_pattern) is a design pattern used in distributed systems and microservices architectures. It addresses the challenges of handling distributed transactions, where actions across multiple services or databases need to be coordinated without atomic guarantees. The pattern involves directing requests, queuing, and retry mechanisms to ensure eventual consistency in a distributed environment.
34
+
35
+
### Programmatic example:
36
+
Here's a programmatic example in Python that demonstrates a simplified implementation of the Commander pattern for handling distributed transactions across two imaginary microservices: payment and shipping. This example uses a basic queuing mechanism and focuses on directing orders between the two services and ensuring idempotence:
37
+
import time
38
+
import random
39
+
40
+
# Mocked databases for payment and shipping
41
+
payment_database = []
42
+
shipping_database = []
43
+
44
+
# Commander class to handle distributed transactions
45
+
class Commander:
46
+
def __init__(self):
47
+
self.queue = []
48
+
49
+
def execute(self, order):
50
+
# Simulate a random failure scenario (e.g., one service is down)
51
+
if random.random() < 0.3:
52
+
print("Service is temporarily unavailable. Queuing the order for retry.")
53
+
self.queue.append(order)
54
+
return
55
+
56
+
# Process the order in one service
57
+
if random.random() < 0.5:
58
+
payment_database.append(order)
59
+
print(f"Order processed by Payment Service: {order}")
60
+
else:
61
+
shipping_database.append(order)
62
+
print(f"Order processed by Shipping Service: {order}")
63
+
64
+
def retry(self):
65
+
print("Retrying queued orders...")
66
+
for order in self.queue:
67
+
self.execute(order)
68
+
self.queue = []
69
+
70
+
# Simulate placing orders
71
+
def place_orders(commander, num_orders):
72
+
for i in range(num_orders):
73
+
order = f"Order {i+1}"
74
+
commander.execute(order)
75
+
76
+
# Create a Commander instance
77
+
commander = Commander()
78
+
79
+
# Simulate placing orders
80
+
place_orders(commander, 10)
81
+
82
+
# Simulate a service recovery scenario
83
+
time.sleep(5)
84
+
print("Service is back online.")
85
+
86
+
# Retry queued orders
87
+
commander.retry()
88
+
In this example:
89
+
90
+
We have two mocked databases (payment_database and shipping_database) to simulate the storage of orders in the payment and shipping services.
91
+
92
+
The Commander class handles the distributed transactions. It directs orders to either the payment or shipping service, simulating the unavailability of services in some cases. If a service is unavailable, orders are queued for retry.
93
+
94
+
The place_orders function simulates placing orders, and the program attempts to process these orders through the Commander.
95
+
96
+
After a simulated service recovery, the retry method is called to process any queued orders.
97
+
23
98
## Credits
24
99
25
100
*[Distributed Transactions: The Icebergs of Microservices](https://www.grahamlea.com/2016/08/distributed-transactions-microservices-icebergs/)
Copy file name to clipboardExpand all lines: cqrs/README.md
+56Lines changed: 56 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -10,6 +10,62 @@ tag:
10
10
## Intent
11
11
CQRS Command Query Responsibility Segregation - Separate the query side from the command side.
12
12
13
+
## Explanation
14
+
### Real-world example:
15
+
16
+
Imagine you are building a complex e-commerce platform. You have a system where customers can place orders, change their account information, and also search for products. In traditional architectures, these operations might all share the same data models and databases. However, as your platform grows, handling these various tasks efficiently becomes a challenge. This is where CQRS comes into play. It allows you to segregate the responsibility for handling commands (e.g., placing orders, updating customer data) from queries (e.g., searching for products). By doing so, you can optimize and scale each side independently to improve overall system performance and manageability.
17
+
18
+
### In plain words:
19
+
20
+
The CQRS (Command Query Responsibility Segregation) pattern is like having two specialized teams for different tasks. One team, the "command" side, focuses on making changes to the system (e.g., handling orders and updates), while the other team, the "query" side, specializes in retrieving information (e.g., searching and reading data). This separation allows you to tailor each side for its specific role, which can lead to better performance, scalability, and maintainability of your software.
21
+
22
+
### Wikipedia says:
23
+
24
+
The [CQRS pattern](https://en.wikipedia.org/wiki/Command%E2%80%93query_separation) (Command Query Responsibility Segregation) is an architectural pattern used in software design. It emphasizes separating the responsibility for handling commands (which change the system's state) from queries (which retrieve information without changing state). By doing so, CQRS promotes a more focused and optimized approach to handling different aspects of an application.
25
+
26
+
### Programmatic example:
27
+
Here's a simplified programmatic example in Python to illustrate the CQRS (Command Query Responsibility Segregation) pattern. In this example, we'll create a basic in-memory "store" where we can issue commands to update data and queries to retrieve data separately:
28
+
# Separate data stores for commands and queries
29
+
command_store = {}
30
+
query_store = {}
31
+
32
+
# Command handlers
33
+
def create_user(user_id, name):
34
+
command_store[user_id] = {"name": name}
35
+
36
+
def update_user_name(user_id, new_name):
37
+
if user_id in command_store:
38
+
command_store[user_id]["name"] = new_name
39
+
40
+
# Query handlers
41
+
def get_user_name(user_id):
42
+
if user_id in query_store:
43
+
return query_store[user_id]["name"]
44
+
return None
45
+
46
+
# Example commands
47
+
create_user(1, "Alice")
48
+
create_user(2, "Bob")
49
+
update_user_name(1, "Alicia")
50
+
51
+
# Example queries
52
+
query_store = command_store.copy() # Snapshot the command store for querying
53
+
54
+
# Retrieve user names
55
+
print(get_user_name(1)) # Output: Alicia
56
+
print(get_user_name(2)) # Output: Bob
57
+
print(get_user_name(3)) # Output: None (user does not exist)
58
+
59
+
In this example:
60
+
61
+
We have two separate data stores: command_store for handling commands and query_store for handling queries.
62
+
63
+
The create_user and update_user_name functions are command handlers responsible for modifying data in the command_store.
64
+
65
+
The get_user_name function is a query handler responsible for retrieving data from the query_store.
66
+
67
+
We issue commands to create and update users, and then we take a snapshot of the command_store to populate the query_store. This separation of stores demonstrates the CQRS pattern's principle of segregating the responsibility for commands and queries.
0 commit comments