From 2c2d8f04694e73d9b93bee7d5174d32737ef83a2 Mon Sep 17 00:00:00 2001 From: Gerard Alonso Date: Wed, 15 Jun 2022 12:01:28 +0200 Subject: [PATCH] Added metadata documentation --- README.md | 4 +- docs/custom_serialization.rst | 6 +- docs/getting_started.rst | 66 ++++++++++++++++++- docs/lifecycle.rst | 4 +- .../replica_service/app/models.py | 8 +-- requirements/docs.txt | 3 +- tests/dj_replica/models.py | 10 +-- 7 files changed, 83 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 8992048..9cd7c36 100644 --- a/README.md +++ b/README.md @@ -91,11 +91,11 @@ class AuthorRef(ReplicaMixin, models.Model): CQRS_CUSTOM_SERIALIZATION = True @classmethod - def cqrs_create(cls, sync, **mapped_data): + def cqrs_create(cls, sync, mapped_data, previous_data=None, meta=None): # Override here pass - def cqrs_update(self, sync, **mapped_data): + def cqrs_update(self, sync, mapped_data, previous_data=None, meta=None): # Override here pass ``` diff --git a/docs/custom_serialization.rst b/docs/custom_serialization.rst index 0860c2b..ece875d 100644 --- a/docs/custom_serialization.rst +++ b/docs/custom_serialization.rst @@ -51,13 +51,13 @@ yourself deserialization for the replica model. class MyReplicaModel(ReplicaMixin): CQRS_ID = 'my_model' CQRS_CUSTOM_SERIALIZATION = True # bypass default deserialization. - + @classmethod - def cqrs_create(cls, sync, mapped_data, previous_data=None): + def cqrs_create(cls, sync, mapped_data, previous_data=None, meta=None): # Custom deserialization logic here pass - def cqrs_update(self, sync, mapped_data, previous_data=None): + def cqrs_update(self, sync, mapped_data, previous_data=None, meta=None): # Custom deserialization logic here pass diff --git a/docs/getting_started.rst b/docs/getting_started.rst index 78146cf..f5dbadb 100644 --- a/docs/getting_started.rst +++ b/docs/getting_started.rst @@ -98,7 +98,6 @@ to the model, you must create a new migration for it: Run your django application --------------------------- - .. code-block:: shell $ ./manage.py runserver @@ -181,3 +180,68 @@ And that's all! Now every time you modify your master model, changes are replicated to all services that have a replica model with the same CQRS_ID. + +Use of customized meta data +=========================== + +The library allow us to send customized metadata from the Master models to the Replica ones. + +Configuring the metadata for Master model +----------------------------------------- + +There are two ways to specify what we want to include in this metadata, overriding the master function or setting a default generic function that will be executed for all masters. + + +Override master function +^^^^^^^^^^^^^^^^^^^^^^^^ + +Inside the Master model class you have to add the **get_cqrs_meta** function that will replace the default one (that returns an empty dict). For instance if you want to return the access of a given model instance inside the metadata you could do the following: + +.. code-block:: python + + def get_cqrs_meta(self, **kwargs): + meta = super().get_cqrs_meta(**kwargs) + if self.is_owner(): + meta['access']['owner'] = True + meta['access']['others'] = False + else: + meta['access']['owner'] = False + meta['access']['others'] = True + return meta + + +Setting a default generic function +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In the django settings you could configure a function that will be executed everytime an event is emitted in any Master: + +.. code-block:: python + + from ... import get_cqrs_meta + + CQRS = { + ... + 'master': { + ... + 'meta_function': get_cqrs_meta, + }, + } + +Retrieving the metadata from the Replica model +---------------------------------------------- + +From the replica model you will now receive an additional parameter called **meta** that will contain all metadata set in the Master model. These data will be present in the following class functions: +* cqrs_update +* cqrs_create +* cqrs_delete + +For instance replacing the **cqrs_update** we could do something like: + +.. code-block:: python + + def cqrs_update(self, sync, mapped_data, previous_data=None, meta=None): + if meta and not meta['access']['owner']: + # Call asynchronously external system to update some resource. + else: + # Call asynchronously internal system to update some resource. + return super().cqrs_update(sync, mapped_data, previous_data, meta) diff --git a/docs/lifecycle.rst b/docs/lifecycle.rst index d592d57..0537d70 100644 --- a/docs/lifecycle.rst +++ b/docs/lifecycle.rst @@ -44,10 +44,10 @@ Message assumed as failed when a consumer raises an exception or returns negativ ... @classmethod - def cqrs_create(cls, sync, mapped_data, previous_data=None): + def cqrs_create(cls, sync, mapped_data, previous_data=None, meta=None): raise Exception("Some issue during create") # exception could be caught at should_retry_cqrs() method - def cqrs_update(self, sync, mapped_data, previous_data=None): + def cqrs_update(self, sync, mapped_data, previous_data=None, meta=None): return None # returning negative boolean for retrying Retrying diff --git a/examples/demo_project/replica_service/app/models.py b/examples/demo_project/replica_service/app/models.py index 44d6114..9bfe135 100644 --- a/examples/demo_project/replica_service/app/models.py +++ b/examples/demo_project/replica_service/app/models.py @@ -36,7 +36,7 @@ def _handle_product_type(mapped_data): return product_type @classmethod - def cqrs_create(cls, sync, mapped_data, previous_data=None): + def cqrs_create(cls, sync, mapped_data, previous_data=None, meta=None): product_type = cls._handle_product_type(mapped_data['product_type']) return Product.objects.create( id=mapped_data['id'], @@ -46,7 +46,7 @@ def cqrs_create(cls, sync, mapped_data, previous_data=None): cqrs_updated=mapped_data['cqrs_updated'], ) - def cqrs_update(self, sync, mapped_data, previous_data=None): + def cqrs_update(self, sync, mapped_data, previous_data=None, meta=None): product_type = self._handle_product_type(mapped_data['product_type']) self.name = mapped_data['name'] self.product_type_id = product_type.id @@ -68,11 +68,11 @@ class Meta: abstract = True @classmethod - def cqrs_save(cls, master_data, previous_data=None, sync=False): + def cqrs_save(cls, master_data, previous_data=None, sync=False, meta=None): cache.set('purchase_' + str(master_data['id']), master_data) return True @classmethod - def cqrs_delete(cls, master_data): + def cqrs_delete(cls, master_data, meta=None): cache.delete('purchase_' + str(master_data['id'])) return True diff --git a/requirements/docs.txt b/requirements/docs.txt index d84d244..5056823 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -4,4 +4,5 @@ Sphinx==3.1.2 sphinx-rtd-theme==0.4.3 sphinx-copybutton==0.2.11 setuptools-scm==3.5.0 -docutils==0.17.1 \ No newline at end of file +docutils==0.17.1 +Jinja2<3.1 diff --git a/tests/dj_replica/models.py b/tests/dj_replica/models.py index 785ca20..21a7be7 100644 --- a/tests/dj_replica/models.py +++ b/tests/dj_replica/models.py @@ -89,7 +89,7 @@ class AuthorRef(ReplicaMixin, models.Model): publisher = models.ForeignKey(Publisher, null=True, on_delete=models.CASCADE) @classmethod - def cqrs_create(cls, sync, mapped_data, previous_data=None): + def cqrs_create(cls, sync, mapped_data, previous_data=None, meta=None): publisher_data, publisher = mapped_data.pop('publisher', None), None if publisher_data: publisher, _ = Publisher.objects.get_or_create(**publisher_data) @@ -100,7 +100,7 @@ def cqrs_create(cls, sync, mapped_data, previous_data=None): Book.objects.bulk_create(Book(author=author, **book_data) for book_data in books_data) return author - def cqrs_update(self, sync, mapped_data, previous_data=None): + def cqrs_update(self, sync, mapped_data, previous_data=None, meta=None): # It's just an example, that doesn't make sense in real cases publisher_data, publisher = mapped_data.pop('publisher', None), None if publisher_data: @@ -130,7 +130,7 @@ class Article(ReplicaMixin): author = models.ForeignKey(AuthorRef, on_delete=models.CASCADE) @classmethod - def cqrs_create(cls, sync, mapped_data, previous_data=None): + def cqrs_create(cls, sync, mapped_data, previous_data=None, meta=None): data = { 'id': mapped_data['id'], 'author_id': mapped_data['author']['id'], @@ -155,8 +155,8 @@ class CQRSMetaModel(ReplicaMixin): id = models.IntegerField(primary_key=True) @classmethod - def cqrs_create(cls, sync, mapped_data, **kwargs): - return kwargs['meta'] + def cqrs_create(cls, sync, mapped_data, previous_data=None, meta=None): + return meta def cqrs_update(self, sync, mapped_data, previous_data=None, meta=None): return sync, mapped_data, previous_data, meta