Dry-struct: how to elegantly perform updates in an object graph of structs?

tl;dr I am searching for an elegant way to perform updates (modify attribute values) in an object graph of structs (nested Dry::Struct objects).

Context

In a Rails application, I am trying to establish a domain layer, inspired by Domain-driven design, where the business logic shall live. That layer shall be free of ActiveRecord (it is oblivious of persistence) and ActiveModel. I chose to build that layer with dry-struct. I like that it proposes to work with immutable objects in a funtional style.

Problem

If you know DDD, you know the basic building blocks like Aggregate, Entity or Value object. While a value object is exactly about immutability, where dry-struct is a perfect fit, an entity or an aggregate are objects with a life cycle. From a conceptual point of view, such objects can change their attribute values throughout their life cycle, while they carry out business logic. This does not immediately fit well with the immutable nature of dry-struct objects.

Question

I am wondering what is the best and most elegant way to perform updates, that means to change the attribute values, of a dry-struct in a possibly deep object graph of structs (like an aggregate). I know that Dry::Struct offers #new as instance method to create a copy with overriden attribute values. But to change a nested struct, I have to chain these #new calls from the toplevel struct (aggregate root) down to the nested struct that I want to change.

Is there a more elegant way to do this? For example Elixir as a functional language has immutable basic data types and really good support in the standard library, like the Kernel.update_in/3 function that solves exactly my use case. Does the dry-rb ecosystem offer something similar?

1 Like

dry-struct is not the right tool for your needs. Its main purpose is to hold only data, without any business logic. dry ecosystem is more suitable for CQRS pattern, since it was design with this in mind. Still, that’s a good question though. IMO if you follow CQRS pattern on top of DDD patterns, then you find drys the right tool

2 Likes

Hmm, your answer fits to my doubts about whether dry-struct is a good choice to implement entities and aggregates. I am still struggling on how to implement a DDD domain layer well. But I got the idea from hanamirb.org, they use dry-struct to implement entities. Yet their guide says nothing further on how to implement the business logic, especially changing attribute values throughout the life cycle of an entity.

You say the dry-rb gems lean more towards the CQRS pattern. Yet another thing to learn :wink:. I know what it means on a high conceptual level, but not (yet) how to implement it. Do you know of any resources explaining how to implement CQRS with dry-rb?

I disagree that this question is about business logic. There must be generally useful patterns for working with immutable data.

@cgrothaus that update_in example is very interesting. I will keep that in mind if I run into this situation; shouldn’t be too hard to implement in Ruby. Dry-rb is an extremely useful toolkit but it is not entirely batteries included, in my experience.

Not yet. Feel free to report a feature request in dry-struct repo. We could support syntax like user.new(group: { name: "New Name" }) that would work with a nested group struct.