Complex software applications usually depend on services and libraries; those services depend on other services and libraries, etc. Within an application, in Ruby language, we use
classes to divide an application into subsystems and those subsystems into more subsystems, etc. How to do it within DCI concepts? Imagine we have "DCI paradigm only" programming language. It means we don't have "class" or "module," and we can only use keywords like
role. I am trying to understand how DCI can "work in scale-free, fractal way." How can we build a complex application when DCI concepts are built on top of DCI concepts on top of DCI concepts, etc.
At first, I had a small misunderstanding about Contexts in DCI. My original understanding was that Context is a script or a procedure or a method where data becomes objects by playing roles (somehow similar to how OOP in C language works). In that case, context maps to use-case one-to-one. And it still might be true in some cases or a more metaphorical sense. But after discussing with
Matthew Browne and other members of awesome DCI community, I fixed my example below.
It appears that
"Each context represents one or more use cases." If Context is a concept that can represent one or more use-cases, then it solves all my issues. If Context can represent more than one use-case, then Context is not precisely a use-case. In a more "nerdy" interpretation, it is a container or scope for use-cases and roles. In that container, I can put other nested contexts too, and my problem is solved.
Note that Ruby language while supporting multiple programming paradigms does not natively support DCI paradigm. I am using classes and modules to express Context, Data and Roles concepts from DCI.
Below is theoretical example sophisticated enough to involve nested Contexts and to cover some of the essential points. Imagine that this is an open world game similar to "The Sims."
GameWorldContext represents application itself and has two nested Contexts (Contexts defined inside of scope of
GameWorldContext is a game where "in-game human actor controlled by a player or AI" can go to a Bank and use its services or go to a Coffee Shop and drink some coffee or eat a donut.
GameWorldContext has a "run" Use-Case which calls other Contexts and their use-cases and executes the application. In our game, we have only one Bank and only one CoffeeShop similarly to how a real game would have only one graphics system and only one sound system.
BankContext has two Use-Cases:
deposit (deposit to a personal bank account) and
invest (deposit to random investment account).
CoffeeShopContext has two Use-Cases:
eat_at_the_table (eat menu item at the table).
Roles and Data definitions have Role and Data suffixes (like
BankAccountData) to make it easier to understand them in context of DCI.
Context can be a Data instance
Context can contain global to that Context Data instances (properties). For example,
investment_accounts properties which are arrays of
BankAccountData instances. This means that when
BankContext gets created (
bank = BankContext.new) it becomes Data instance too. Context as Data instance can play Roles within Use-Cases.
While DCI concepts like Data, Role, Context can be expressed by concepts like
namespace in OOP languages(which are more class-oriented programming than OOP), pure DCI language would not require them. In pure DCI language, we can remove concepts of
namespace and replace them with keywords like
context. We can enforce additional constraints to prevent less experienced developers from making architectural mistakes. For example, Data definition should not include code that is an interaction between other Data instances which are not part of a state of current Data instance or using Roles in a code of Data. Similar constraints can be applied to
role. In other words, pure DCI programming language can enforce good DCI architectural style. Another benefit of a pure DCI language would be an ability to add syntax sugar that would make programming in DCI paradigm enjoyable and efficient.