On Phoenix Contexts, DDD and BDD

I had a couple of questions around contexts and best practices, or at the very least I’m trying to validate the my understanding of the subject is okay.

As I understand Contexts or Bounded contexts are nothing more than a code design practice that Phoenix 1.3 is introducing and laxly enforcing, the core concept makes sense and in principle it sounds like a good practice to set boundaries between the multiple components of an application.

What I’m finding is that the idea of contexts is fairly abstract and it only gets murkier when trying to put into practice, DDD in particular cares a lot (primarily) about the domain core and for someone that has no experience trying to define boundaries let alone optimal boundaries can be challenging, specially without experience.

Things can get even more challenging on situations where there might be overlap and the boundaries are not as clear or easily defined, for example a table or data needs to be accessed from multiple contexts (users would be a good example) I’ve seen a lot of mixed opinions on how to best tackle the problem.

As I see it right now Contexts just introduced a lot of confusion and uncertainty to developers, mostly because of the lack of experience and the introduction of particular way of designing/architecting code that most people will find strange.

The other thing that keeps jumping on the back of my mind is the idea of “Modelling by Example” introduced by Konstantin Kudryashov a while back ago in the php community, it tries to marry BDD+DDD and it does into what I think is a pretty elegant fashion, check it out here:

And the following talk on BDD:

Sorry for the mini mental dump. Curious to hear what other people think.

Yes, contexts are a design practice that the phx.gen.html|json|context use to help newcomers think about application design.

It is an abstract set of a practices and I agree this has tripped some people up. When it comes down to it, we can’t write the application for our users, and writing any nontrivial app to model a given domain can’t be distilled into “follow these steps and you’re done”. What we can do is nudge people into proper mindset when designing an application to set them up for long-term success. We can, and will in the upcoming guides, show suggestions on general practices to follow for common scenarios, but not everything can be distilled into a hard rule-set.

Our 1.3 guides will review some of these challenges and offer possible solutions, as well as what to think about when depending on data access across contexts. So stay tuned on that front.

Our guides will show common cross-context challenges and solutions specifically around your “users” point. You see mixed opinions in part because your data access layer at large is going to be dependent on your application and the tradeoffs you want to make. Sometimes a SQL join is exactly what you want. For example, generating complex aggregate reports across lots of tables in your database that happen to span many contexts. Efficiently trying to aggregate this data by some exposed API on your contexts would be very difficult or impossible to do efficiently. On the other hand, if you can easily avoid joining across the context then your system can grow with fewer dependency and maintenance burden. You’ll see different opinions based on different use-cases, but hopefully when our 1.3 guides drop we’ll iron out some common solutions for you to think about.

I think much of the confusion and uncertainty is because we haven’t yet provided the guides and shipped a 1.3 final so folks are going partially blind. One point I want to highlight here is the context generators were made exactly to address “the lack of experience of a particular way of designing code”. They are learning tools specifically to help folks build a mental model about designing their greater application.

I also want to re-iterate while I used the “bounded context” definition from Folwer in one of my talks, it was to show where we were taking some of these ideas from, but we aren’t asking folks to go find a 400 page DDD book and apply a particular pattern.

When shall we expect the 1.3. guides? It seems that the relatively long transition phase from 1.2 to 1.3 is holding back the development of new projects.

In any case, the community appreciates the excellent work of the core team!

1.3 has been a release candidate for so long everyone’s forgotten it’s a release candidate. (Not that the wait concerns me.)

Welcome to my hell if you have to do this. ^.^;
/me works with an ancient Oracle database that was apparently designed by intellect-less committee…
Avoid it if you can!
If you cannot though, look at Absinthe. I’ve been hiding the hell behind it’s GraphQL interface and I make GraphQL calls from in-server to in-server, it has become an excellent ‘Context’ in the system for data calls distributed across everything. ^.^

3 Likes

Actually there is a reason the OLTP vs OLAP dichotomy evolved. Your domain runs against a set of schemas on the online transactions processing systems side while your reporting, analysis, and intelligence runs on the online analysis processing side - typically reorganized in star schemas which are optimized towards the needs of data warehousing. So for reporting purposes data is duplicated from the OLTP system to the OLAP systems (Philip Greenspun: Data Warehousing for Cavemen (1997), Data Warehousing (2003)).

So it wouldn’t be too outlandish to suggest that for reporting purposes data should be “exported” to separate reporting schemas. In Phoenix when running multiple contexts against the same database one has the “luxury” of using a SQL join - but at the same time one has to be aware that one is engaging in an integration database.

When a context is promoted to a microservice this has to stop because an integration database violates service autonomy (a notion of “context autonomy” is probably appropriate as a context is all about cohesion). The typical solution to serve reporting needs is an Event Data Pump (Building Microservices p. 98) - while conducting it’s responsibilities each domain microservice emits events that are processed separately to be included in a reporting database - and that reporting database is used by the reporting service.

One interesting thing about contexts is that there are times where contexts legitimize violating DRY - but only when necessary in order to promote loose coupling because DRY can lead to tight coupling.

Development By Slogan with DRY (2015)

2 Likes

Right, there are certainly prior art to look at for a robust reporting system. The “tradeoffs” I mentioned revolve around these ideas you’ve shared. You can either 1) perform complex - but easy to write - SQL joins, or 2) re-architect your system with Event Data Pumps, et al. I agree this reporting use-case done “right”, would definitely not crunch on live data, rather exported or against a mirrored DB, but this is a separate conversation from contexts, where we’re talking about contexts as starting points. The vast majority of folks just want to crunch on and report against live data, without going down the rabbit hole of the mentioned material.

So our goal with contexts isn’t to solve the question of “when to use Event Data Pumps vs SQL joins”, but rather set folks up from the start with an external context API that allows them to grow their systems to these places without having to rewrite their entire application. So if they are set up from day one with SalesReports.list_daily_revenue(30), if we later grow this to crunch against an exported data store just for reporting, our callers don’t care. If folks are thinking about isolation and decoupling up front, they would be in a much better position to grow their system into the different data warehousing architectures you mentioned, should they require it.

I fully understand where you are coming from … but at the same time we have to acknowledge that from a beginner’s perspective setting up boundaries to isolate contexts while at the same time violating these boundaries with SQL joins creates a bit of cognitive dissonance.

The only way to remove that is to emphasize:

  • contexts serve only the domain
  • while reports/analysis/intelligence are legitimate user features they typically aren’t considered part of the core domain
  • therefore reports are a concern separate from the core domain (and the goals contained therein)
  • therefore SQL joins for reporting purposes aren’t viewed as violating any boundaries**
  • and ultimately those reporting SQL joins need to be considered a “shortcut” because that approach to reporting simply doesn’t scale. The perception of boundaries being violated is gone once reporting is moved to a separate database (not with a mirrored schema - but with a data warehousing schema).

**However there still is contention in the sense that whenever you change the context’s schema you also have to update your reporting query.

2 Likes