Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Working Example Project #19

Open
seun-otosho opened this issue May 14, 2023 · 3 comments
Open

Working Example Project #19

seun-otosho opened this issue May 14, 2023 · 3 comments

Comments

@seun-otosho
Copy link

Hi,

Well done for this great project.

I would like to request for a working examples of complete project, this would help greatly in understanding fully how the project is implemented in django

Many thanks in advance

@johnbywater
Copy link
Contributor

Hi @seun-otosho! Thanks for interest and your kind words. I'm sorry for the delay in replying.

There is actually a Django project in the test folder of this repository, which has enough to show you what to do.

After that, it is really just a matter of doing standard Django things, albeit without the View/ORM integration classes. It works really well. The techniques are just standard Django things. Basically, you if you need to write an interface, and if you want or need to do it with Django, then you just need to know how to use Django. There's loads of documentation for Django. You can certainly use other features of Django, such as the user model(s), for authentication and access control. You can have a separate database for that, or put the different tables from different Django apps in the same database. It's kind of just normal Django techniques. I agree that it would be nice to have a big example project, but it would need maintaining, and it might get old quickly. And it might be hard for anybody to pick out the few things they need and don't understand.

But of course, if you have specific questions about how to get started, or would like to have a more general chat about things, we're here to help!

@fbinz
Copy link

fbinz commented Jan 12, 2025

Maybe on a related note:

I've sometimes used the event sourcing pattern in an ad-hoc manner in Django projects. In my case, this was the classical example of a "bank account" of sorts, where the balance is calculated based on a stream of transactions, the transactions being the events (i.e. source of thruth) for the balance.

What this library seems to provide, is your eventsourcing Python package with the Django ORM as a storage backend.

Coming from a Django background, how could we still leverage the ORM (and the database for that matter), for constraints? For example, let's say we want to have a BankAccount model, that always has a positive balance. In Django, we'd model it like this:

class BankAccount(models.Model):
    balance = models.DecimalField()
    class Meta:
        constraints = [models.CheckConstraint(Q(balance__gt=0))]

This would prevent us, on a database level, to get negative balances.

Can this be combined with the django-eventsourcing package?
I.e. is something like this somehow possible?

class BankAccount(Aggregate, models.Model):
    balance = models.DecimalField()
   
    @event("Transaction")
    def transfer(self, amount: Decimal):
        self.balance += amount
        self.save()

    class Meta:
        constraints = [models.CheckConstraint(Q(balance__gt=0))]

Trying it out, the main issue is that both Aggregate and models.Model use a metaclass, which introduces the following error message:

metaclass confict: the metaclass of a dervied class must be a (non-strict) subclass of the metaclasses of all its bases

What's your take on this?
Would it be a bad idea, to build something like this? Is this intended to be used differently?

@johnbywater
Copy link
Contributor

Hi @fbinz,

That's an interesting idea.

My take on this is that the eventsourcing-django package provides a persistence module for the eventsourcing package, that uses the Django ORM to implement the persistence layer. An event sourced aggregate is something that belongs to the domain layer. And because the domain layer should be independent of the persistence layer, it would be better not to refer to things from the Django ORM in the event-sourced aggregates.

To explain this a bit more: In an eventsourcing project, the domain layer has aggregates and domain events, and the persistence layer has stored events. The domain layer doesn't know anything about stored events. And the persistence layer doesn't know anything about the aggregates and the domain events.

The domain events are persisted as stored events, and there's a mapper that converts domain events to stored events, and stored event to domain events. Aggregates are reconstructed from domain events that are reconstructed from stored events. So that we can use different databases, there are different persistence modules, which all record and retrieve stored events in different ways. There are various persistence modules for the eventsourcing package that adapt different persistence technologies. For example, there are persistence modules for storing events directly in SQLite, or directly in PostgreSQL, or directly in SQLAlchemy, or directly in the Django ORM.

SQLAlchemy and the Django ORM are ORMs, and so can be used to store events in SQLite, PostgreSQL, and many other relational database management systems.

In Django, it's slightly different. We don't usually talk about domain layers and persistence layers. We just code a "model" using the ORM, which can then be persisted in a range of different databases according to the project's settings.

What makes the eventsourcing-django package appealing is the wide range of databases supported by Django, and also the unified approach to managing databases (migration, test databases, centralised configuration). But in the eventsourcing package, the Django ORM is being used as a persistence technology.

Generally speaking, when coding a domain model, constraints that an event-sourced aggregate should enforce should be coded within the aggregate. It's generally a good idea to code the aggregate to work independently from any persistence mechanism. And so, typically, the balance constraint would be coded in command methods that would trigger events that change the balance. There's an example of this in the documentation.

If you develop your event-sourced aggregates to be independent of any persistence mechanism, you can develop and test them more easily, and you can use your aggregates with any persistence modules, not just the Django ORM.

It's possible in a Django project to use the eventsourcing package with a different persistence module, and there might be good reasons for not using the Django ORM to persistent events, such as for performance. But at least to begin with, if you have a Django project, and have other models, it's convenient to just do everything, including storing the events of your event-sourced aggregates, using the Django tools and methods.

Hope that makes sense and is helpful?

All good wishes,

John

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants