{ "info": { "author": "Victor Klapholz", "author_email": "victor.klapholz@gmail.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy" ], "description": "************************************\nDomain Driven Design - Domain Events\n************************************\n\nInspired by `Udi Dahan's fabulous Domain Events `_\nimplementation in particular, and by `Domain Driven Design `_\nbest practices in general.\n\nThe DDD Domain Events package makes it easy to:\n - Register to **Domain Events** from the **Application Layer**\n - Raise **Domain Events** from the **Domain Layer** so they can be handled in the **Application Layer**\n\nThe *Domain Events* are local to the execution thread\n(via Python's `threading.local `_)\nand hence are thread specific.\n\n\nInstalling ddd-domain-events\n----------------------------\n\n.. code-block:: bash\n\n pip install ddd-domain-events\n\n\nWhy should you consider using Domain Events?\n--------------------------------------------\nDomain Events are based on the `Observer Design Pattern `_, which is a\nconvenient way to decouple your *Domain Layer Business Logic* from the *Application Layer* code - especially as the\nDomain code should have no *Infrastructure Layer* dependencies (such as calling Repositories or Providers that call\nexternal services).\n\nIn other words, your *Domain Entities* should have no access to the *Application Layer Services* - and\nhence, your *Entities* might use *Domain Events* to indirectly communicate with *Application Services*.\n\nFor example, say you have a Gaming Domain with a User entity that has *add_points(number_of_points)* behavior.\nWhenever *add_points* is executed, the obvious expected behavior is to add the provided number of points to the\nspecific user. However, in our sample game, whenever a user reaches 1,000 points then she should receive the \"Master\"\nbadge, and whenever the user reaches 1,000,000 points then she should receive the \"Champion\" badge.\n\nYour Application Layer pseudo python code might look like this:\n\n .. code-block:: python\n\n user.add_points(10,000)\n if user.has_reached_master_level:\n # TODO: Send the user a congratulation email...\n elif user.has_reached_champion_level:\n # TODO: Send the user both a congratulation email and a check...\n\nThe above code is perfectly o.k. - yet, as there will be more actions / options that\nare the consequence of *add_points*, then the code will become more and more cumbersome.\n\nUsing the Domain Events alternative, will allow you to write code that looks more like this pseudo code:\n\n .. code-block:: python\n\n domain_events.register_event(has_reached_master_level_callback)\n domain_events.register_event(has_reached_champion_level_callback)\n\n user.add_points(10,000)\n\nThis code is cleaner and arguably easier to extend, as shown bellow:\n\n .. code-block:: python\n\n domain_events.register_event(has_reached_master_level_callback)\n domain_events.register_event(has_reached_champion_level_callback)\n # introduce new level...\n domain_events.register_event(has_reached_intermediate_level_callback)\n\n user.add_points(50)\n\nLast but not least, Domain Events allow you to keep your Entity's code cleaner - with less methods\n(such as *has_reached_master_level* and *has_reached_champion_level* which can be omitted) that might be\nrelatively complex - as they might require you to use additional state in the entity (so that you know if\nthe user has reached the current level following the recent call to *add_points*).\n\n\nUsing DDD Domain Events\n-----------------------\n\nUsing Domain Events in an existing Application Service can easily be achieved by using Python's **with-statement**.\n\nFor example:\n\n**Somewhere in the Application Layer...**\n\n .. code-block:: python\n\n with DomainEvents() as domain_events:\n # create a callback to the notify_top_management Application Layer function\n high_price_volume_callback = DomainEventCallable(OrderEvent.HIGH_VOLUME_PRICE, notify_top_management),\n\n # register callback - so it can be triggered from the Domain Layer\n domain_events.register_event(high_price_volume_callback)\n\n # create Domain Entity\n order = Order()\n\n # execute a Domain method that might raise the relevant Domain Event\n order.add_order_items(order_items)\n\n\n**Somewhere in the Domain Layer...**\n\n .. code-block:: python\n\n # Domain entity raises a Domain Event - allowing the Application Layer\n # to take a relevant action.\n DomainEvents.raise_event(OrderEvent.HIGH_VOLUME_PRICE, order=self)\n\n\nHow it works\n------------\n\nBellow is a simplified example that should help you understand how and when you might choose to use **Domain Events**.\n\n\n *Step 1*: Define a **Domain Event Type** in your **Domain Layer**\n\n .. code-block:: python\n\n from ddd_domain_events import DomainEvents, DomainEventCallable\n\n class OrderEvent(Enum):\n \"\"\"Domain Event raised for special order use cases\"\"\"\n HIGH_QUANTITY = 'HIGH_QUANTITY'\n HIGH_VOLUME_PRICE = 'HIGH_VOLUME_PRICE'\n\n\n Step 2: Define a **Domain Entity** that raises Domain Events\n\n .. code-block:: python\n\n class OrderItem:\n \"\"\"OrderItem value object that contains order details for a single item\"\"\"\n def __init__(self, product_id: str, price: float, quantity: int):\n self.product_id = product_id\n self.price = price\n self.quantity = quantity\n\n class Order:\n \"\"\"Order entity that contains order items\"\"\"\n HIGH_VOLUME_PRICE = 1_000_000\n HIGH_QUANTITY = 10_000\n\n def __init__(self):\n self._order_items = []\n\n @property\n def order_items(self):\n for order_item in self._order_items:\n yield order_item\n\n def add_order_items(self, order_items: List[OrderItem]) -> None:\n total_price = 0\n total_quantity = 0\n\n for order_item in order_items:\n total_price += (order_item.price * order_item.quantity)\n total_quantity += order_item.quantity\n # Process the actual business logic related to this method,\n # which is add OrderItem value objects to this Order Entity\n self._order_items.append(order_item)\n\n # Notify whoever might be interested about high price volume orders\n if total_price >= self.HIGH_VOLUME_PRICE:\n DomainEvents.raise_event(OrderEvent.HIGH_VOLUME_PRICE, order=self)\n\n # Notify whoever might be interested about high quantity volume orders\n if total_quantity >= self.HIGH_QUANTITY:\n DomainEvents.raise_event(OrderEvent.HIGH_QUANTITY, order=self)\n\n *Step 3*: Define an **Application Service** that registers to **Domain Events**\n\n .. code-block:: python\n\n class OrderService:\n \"\"\"Application Service for handling Order related operations\"\"\"\n @classmethod\n def create_order(cls, order_items: List[OrderItem]) -> Order:\n with DomainEvents() as domain_events:\n # Create callbacks for 'side effects' that are related to domain logic,\n # and which should be handled by the Application Layer\n callbacks = [\n DomainEventCallable(OrderEvent.HIGH_VOLUME_PRICE, cls.notify_top_management),\n DomainEventCallable(OrderEvent.HIGH_VOLUME_PRICE, cls.notify_sales_team),\n DomainEventCallable(OrderEvent.HIGH_QUANTITY, cls.notify_inventory_team)\n ]\n\n # Register for these domain events\n for callback in callbacks:\n domain_events.register_event(callback)\n\n order = Order()\n\n order.add_order_items(order_items)\n\n return order\n\n @staticmethod\n def notify_sales_team(order: Order) -> None:\n \"\"\"A callback for notifying the sales team about the important order\"\"\"\n\n @staticmethod\n def notify_top_management(order: Order) -> None:\n \"\"\"A callback for notifying the top management about the important order\"\"\"\n\n @staticmethod\n def notify_inventory_team(order: Order) -> None:\n \"\"\"A callback for notifying the inventory team required quantities\"\"\"", "description_content_type": "", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://gitlab.com/py-ddd/ddd-domain-events", "keywords": "Domain Driven Design", "license": "MIT", "maintainer": "", "maintainer_email": "", "name": "ddd-domain-events", "package_url": "https://pypi.org/project/ddd-domain-events/", "platform": "", "project_url": "https://pypi.org/project/ddd-domain-events/", "project_urls": { "Homepage": "https://gitlab.com/py-ddd/ddd-domain-events" }, "release_url": "https://pypi.org/project/ddd-domain-events/0.1.6/", "requires_dist": null, "requires_python": "", "summary": "Domain Driven Design: ", "version": "0.1.6" }, "last_serial": 3800030, "releases": { "0.1.0": [ { "comment_text": "", "digests": { "md5": "a7f55c0f1d61a3b60107021918efb248", "sha256": "a60e56d6cd294eedbc65051b94163dac687056e2f96d9f96c3dd5abf161e2af6" }, "downloads": -1, "filename": "ddd-domain-events-0.1.0.tar.gz", "has_sig": false, "md5_digest": "a7f55c0f1d61a3b60107021918efb248", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 3950, "upload_time": "2018-04-21T15:23:54", "url": "https://files.pythonhosted.org/packages/26/d0/d75225173b6712cba4ac056ad0c5f7cafdd685532411845ba0cf83fe51cf/ddd-domain-events-0.1.0.tar.gz" } ], "0.1.1": [ { "comment_text": "", "digests": { "md5": "7f06d4e9bafb4998c1479e2521dc24fc", "sha256": "9f0414d6a318f867717dfacc8d1d5ddcc616093af418e3f6042eb576ca6458c1" }, "downloads": -1, "filename": "ddd-domain-events-0.1.1.tar.gz", "has_sig": false, "md5_digest": "7f06d4e9bafb4998c1479e2521dc24fc", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 5234, "upload_time": "2018-04-21T16:39:33", "url": "https://files.pythonhosted.org/packages/10/e2/831e1956ae8877bd80c674ecab2cff973a228c3c6c43018accf8fdc7ba67/ddd-domain-events-0.1.1.tar.gz" } ], "0.1.2": [ { "comment_text": "", "digests": { "md5": "b722cd40c4661cd00ae2d5ea9c01e3cd", "sha256": "0685edcb780dfd33d7c0e5c9b7a3a5431cf6a88c868acbf13187faf622d4d5b9" }, "downloads": -1, "filename": "ddd-domain-events-0.1.2.tar.gz", "has_sig": false, "md5_digest": "b722cd40c4661cd00ae2d5ea9c01e3cd", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 5237, "upload_time": "2018-04-21T16:45:33", "url": "https://files.pythonhosted.org/packages/8a/84/b2a86ad01a29820578a11a87b879799a11eaf35a10e6e42de6087d862f4c/ddd-domain-events-0.1.2.tar.gz" } ], "0.1.3": [ { "comment_text": "", "digests": { "md5": "5c2df15ff8ca8b9059ff6c61fd153de8", "sha256": "68162dae96bb46f0982bdbec03055853d3e50facf39ac9256e8d87c9465f246b" }, "downloads": -1, "filename": "ddd-domain-events-0.1.3.tar.gz", "has_sig": false, "md5_digest": "5c2df15ff8ca8b9059ff6c61fd153de8", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 5179, "upload_time": "2018-04-21T16:51:13", "url": "https://files.pythonhosted.org/packages/8b/cb/95af33a13e6d6d074282402d0e5b762b418b0bbf4aa5aee68267bd32736c/ddd-domain-events-0.1.3.tar.gz" } ], "0.1.4": [ { "comment_text": "", "digests": { "md5": "537aa5c57fc8535f3c080df2d7588a78", "sha256": "1f3c59697fb126a6992b04bcc942b76d4cafacf396887fc33c89648581ba9bab" }, "downloads": -1, "filename": "ddd-domain-events-0.1.4.tar.gz", "has_sig": false, "md5_digest": "537aa5c57fc8535f3c080df2d7588a78", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 5223, "upload_time": "2018-04-21T16:55:57", "url": "https://files.pythonhosted.org/packages/3c/a8/89cc6146e19fa382c251ef0787506079912500476c24cffb0787c1576204/ddd-domain-events-0.1.4.tar.gz" } ], "0.1.5": [ { "comment_text": "", "digests": { "md5": "c00be250ae74171e7e391ca4c0d0e25d", "sha256": "30e274056bacaad27b269cbc28e248e736a26e9e79fd265367105e24aa7dc50d" }, "downloads": -1, "filename": "ddd-domain-events-0.1.5.tar.gz", "has_sig": false, "md5_digest": "c00be250ae74171e7e391ca4c0d0e25d", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 5247, "upload_time": "2018-04-23T18:28:41", "url": "https://files.pythonhosted.org/packages/a3/d9/27a16d45a34b3bfa80b08f4ead7f0d63994d722151d9de7cbcf75df2a176/ddd-domain-events-0.1.5.tar.gz" } ], "0.1.6": [ { "comment_text": "", "digests": { "md5": "4fe83f992a3f1b9e6ed5138c1b21e48b", "sha256": "2c548f967e256ff17c7b00184975623936eeb2a308502b335822d66a59785b6c" }, "downloads": -1, "filename": "ddd-domain-events-0.1.6.tar.gz", "has_sig": false, "md5_digest": "4fe83f992a3f1b9e6ed5138c1b21e48b", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 5265, "upload_time": "2018-04-23T18:49:46", "url": "https://files.pythonhosted.org/packages/39/d9/7ea6a31d0e61f043d4503cdfe4bac2abd57e0161d26a7525a54ee30d6df7/ddd-domain-events-0.1.6.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "4fe83f992a3f1b9e6ed5138c1b21e48b", "sha256": "2c548f967e256ff17c7b00184975623936eeb2a308502b335822d66a59785b6c" }, "downloads": -1, "filename": "ddd-domain-events-0.1.6.tar.gz", "has_sig": false, "md5_digest": "4fe83f992a3f1b9e6ed5138c1b21e48b", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 5265, "upload_time": "2018-04-23T18:49:46", "url": "https://files.pythonhosted.org/packages/39/d9/7ea6a31d0e61f043d4503cdfe4bac2abd57e0161d26a7525a54ee30d6df7/ddd-domain-events-0.1.6.tar.gz" } ] }