Skip to content

Commit

Permalink
minor improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
gustahrodrigues committed Mar 2, 2023
1 parent 4426b80 commit d8fa7ec
Showing 1 changed file with 10 additions and 10 deletions.
20 changes: 10 additions & 10 deletions content/posts/2022-09-14-introducing-jaiminho/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ date: 2022-11-25
comments: true
---

At Loadsmart, many of our services are organized within an event-driven architecture and face some usual challenges that come with such an architecture. One of them is **dual writes**, which happens when an application has to write data on two different places. A common example of dual writes is an application that needs to store data on a database and notify that change by emitting an event which may be consumed by different services. The inherent issue with this scenario is that if one of these two operations fails, the producer and consumer applications will have data inconsistencies. If we choose to write the change to the database first and then notify the event, in the case of a notification failure the database would have the updated data and the consumer wouldn’t. If we reverse it (i.e. notifying first and then storing), a failure when committing the transaction in the database would cause the consumer receiving data that isn’t on the application database.
At Loadsmart, many of our services are organized within an event-driven architecture and face some usual challenges that come with such an architecture. One of them is **dual writes**, which happens when an application has to write data in two different places. A common example of dual writes is an application that needs to store data on a database and notify that change by emitting an event that may be consumed by different services. The inherent issue with this scenario is that if one of these operations fails, the producer and consumer applications will have data inconsistencies. If we choose to write the change to the database first and then notify the event, in the case of a notification failure, the database would have the updated data and the consumer wouldn’t. If we reverse it (i.e., notifying first and then storing), a failure when committing the transaction in the database will cause the consumer to receive data that isn’t on the application database.

There are well-known methods to deal with such issues, and we chose to apply the transactional outbox pattern. Since we couldn’t find any related solution on [Django community](https://djangopackages.org/search/?q=outbox), we decided to implement an open-source library called [Jaiminho](https://github.com/loadsmart/jaiminho), which is a broker agnostic implementation of the outbox pattern.
There are well-known methods to deal with such issues, and we chose to apply the transactional outbox pattern. Since we couldn’t find any related solution in the [Django community](https://djangopackages.org/search/?q=outbox), we decided to implement an open-source library called [Jaiminho](https://github.com/loadsmart/django-jaiminho)(available through [Pypi](https://pypi.org/project/django-jaiminho/)), a broker-agnostic implementation of the outbox pattern.


# The Transactional outbox pattern
Expand All @@ -23,7 +23,7 @@ For simplicity purposes, let’s assume the common case of an application that h

- Outbox table

When storing a record to the database, the corresponding event should be persisted into an outbox table, and both operations should be in the same transaction. With this strategy we can leverage the atomicity property of relational databases transactions to ensure either both or no inserts will be successful.
When storing a record in the database, the corresponding event should be persisted into an outbox table, and both operations should be in the same transaction. With this strategy, we can leverage the atomicity property of relational database transactions to ensure either both or no inserts will be successful.

- Message relayer

Expand All @@ -39,16 +39,16 @@ With the default configuration, when you add the `@save_to_outbox` decorator to
- The method that was used for sending that message
- The extra kwargs used to call the method

This data will allow for the message to be sent again using the original method, message and kwargs. Also, at the end of the decorated method a Django signal will be sent to indicate the event success or failure.
This data will allow for the message to be sent again using the original method, message and kwargs. Also, at the end of the decorated method a Django signal will be sent to indicate the event's success or failure.

Since the message and kwargs are serialized into bytes, any type of message is supported. For example, in Loadsmart we work with JSON and Protobuf messages, and both are using Jaiminho with no need of special configurations.
Since the message and kwargs are serialized into bytes, any type of message is supported. For example, in Loadsmart we work with JSON and Protobuf messages, and both are using Jaiminho with no need for special configurations.

## Publish strategies

With Jaiminho, you can choose between the following two publish strategies, using the `PUBLISH_STRATEGY` configuration:

### Keep Order
This strategy is similar to the transactional outbox [described by Chris Richardson](https://microservices.io/patterns/data/transactional-outbox.html). The decorated function intercepts the function call and saves it on local DB to be executed later. A separate event relayer will keep polling local DB and executing those functions in the same order it was stored.
This strategy is similar to the transactional outbox [described by Chris Richardson](https://microservices.io/patterns/data/transactional-outbox.html). The decorated function intercepts the function call and saves it on the local DB to be executed later. A separate event relayer will keep polling local DB and executing those functions in the same order it was stored.
Be careful with this approach, **if any execution fails, the relayer will get stuck**. Otherwise, it would not possible to guarantee delivery order.

### Publish on commit
Expand All @@ -61,7 +61,7 @@ Besides the publish strategies, there are two other options that can be configur

### Persist all events

While the default behavior of Jaiminho makes losing an event less likely, it still has a point of failure: if the notifying method fails and Jaiminho receives an error trying to store the database entry, the event will be lost. In order to avoid this possibility, the `PERSIST_ALL_EVENTS` configuration can be set to True, which will save the database entry *before* trying to notify the event. In this case, the notifying method will be called when the database transaction is committed, which will ensure that the event is persisted. This is not applicable when the publish strategy is **Keep order**, because all the messages must be stored to achieve this strategy.
While the default behavior of Jaiminho makes losing an event less likely, it still has a point of failure: if the notifying method fails and Jaiminho receives an error trying to store the database entry, the event will be lost. In order to avoid this possibility, the `PERSIST_ALL_EVENTS` configuration can be set to True, which will save the database entry *before* trying to notify the event. In this case, the notifying method will be called when the database transaction is committed, which will ensure that the event is persisted. This is not applicable when the publishing strategy is **Keep order**, because all the messages must be stored to achieve this strategy.

### Delete after send

Expand All @@ -75,7 +75,7 @@ Jaiminho comes with two Django Commands that can be useful:

### Event Relaying

The Event Relaying command will query the database for all events that were not sent in a given timebox and will try to re-send them using the original method, message and kwargs. Note that if the event relay runs after a deploy that changed or removed the original method, it will not be able to re-send the failed events and they will have to be dealt with manually. Also, the `DELETE_AFTER_SEND` configuration also applies to this command.
The Event Relaying command will query the database for all events that were not sent in a given timebox and will try to resend them using the original method, message and kwargs. Note that if the event relay runs after a deployment that changed or removed the original method, it will not be able to resend the failed events and they will have to be dealt with manually. Also, the `DELETE_AFTER_SEND` configuration also applies to this command.

To ensure all failed events are re-sent by Jaiminho, you have two options with the Event Relaying command:

Expand All @@ -100,7 +100,7 @@ To add Jaiminho to your project, you have to:
1) Install it:

```console
python -m pip install jaiminho
python -m pip install django-jaiminho
```

2) Add it to the Django project’s `INSTALLED_APPS`:
Expand Down Expand Up @@ -152,4 +152,4 @@ python manage.py events_relay loop_interval 15

# Final remarks

If you are interested in learning more, there is a detailed documentation on the [GitHub repository](https://github.com/loadsmart/jaiminho). Also, contributions are more than welcome! Feel free to contribute if you find any bugs or have any suggestions.
If you are interested in learning more, there is a detailed documentation on the [GitHub repository](https://github.com/loadsmart/django-jaiminho). Also, contributions are more than welcome! Feel free to contribute if you find any bugs or have any suggestions.

0 comments on commit d8fa7ec

Please sign in to comment.