3 ms·
The #1 canary for me is circular dependencies. In more complex domains, a strict hierarchy of types begins to fall apart rapidly. In my experience, a relationa
by bob1029 3y ago
The #1 canary for me is circular dependencies. In more complex domains, a strict hierarchy of types begins to fall apart rapidly.
In my experience, a relational model is the best solution for domain modeling precisely because it can directly represent circular dependencies without any sort of weird interface hacks, one off Init() methods, etc.
A practical example of this would be something like the domain of banking. In a bank, you generally have 2 major types of entities: accounts and customers. Accounts can have many customers and customers can have many accounts. Which type should "win"? In SQL you don't have to make this decision at modeling time - it is deferred to the caller and its specific view of the world.
You can certainly emulate this in OOP, but then you are stuck doing joins between collections manually.
- Xcelerate 3y agoThis is why I prefer multiple dispatch over classes: functions dispatch on the types of all arguments, not just the type of the implicit first argument. After first encountering this paradigm in Julia, it’s painful to use other languages that don’t have this feature.
- JanisErdmanis 3y agoI don’t think multiple dispatch solves this issue. Julia does have a strong type hierarchy and the proposed example with customers and accounts would be likewise hard to model. IMO only Rust like traits solves this issue elegantly.
- bob1029 3y ago> solves this issue elegantly What about cycles? For example, "List all distinct customers from all accounts that are owned by customers who are members of some other subset of accounts".
- JanisErdmanis 3y agoI agree Rust does not make it easier either. This is an interesting design problem. I tried it with ChatGPT and it did produce a robust solution. This does not seem to be a limitation for strong type hierarchy after all: ``` # Define a struct for Customer struct Customer id::Int name::String # Other customer-related fields end # Define a struct for Account struct Account id::Int balance::Float64 type::String # e.g., "Savings", "Checking" # Other account-related fields end # Define a struct to manage the many-to-many relationship struct CustomerAccount customer::Customer account::Account end # Define a struct for the Bank which holds all information struct Bank customers::Dict{Int, Customer} accounts::Dict{Int, Account} customerAccounts::Vector{CustomerAccount} end ```
- bob1029 3y ago> it did produce a robust solution. Indeed. It produced a relational solution that emulates what you'd do with SQL tables. This is approximately the schema shape we use in our product today.
- nradov 3y agoI don't see the problem. You can have a class for accounts, a class for customers, and a class for the links between accounts and customers. Those classes are peers; there's no need for one to be the winner. Most OOP languages also have ORM frameworks that can help here. I've built stuff like that in Java and in practice it's just not issue.