1

To use a dumb example, assume that particular users (labourers) can move boxes around. How would you go about representing that in your system in a semantic way that scales. I'm currently inclined to do the following.

class User {
 // ....

  lift(box: Box) {
    if(this.role === UserRoles.Labourer) {
      box.setPosition(box.xPosition, box.yPosition + 10);
    } else {
      // whatever
    }
  }
}

I was thinking of going in this direction because it captures the semantic that a user can lift a box. But since the change is actually happening to the box, we still need a method to change the box's properties (i.e create a setPosition method to set location of the box). Do you think this decision is appropriate? Would you model interactions like this by providing a method on the subject of the verb along with a corresponding method on the object? I'm asking this as I am trying to capture semantics of my business in code in an expressive way and wonder if this was ever something that was thought of.

In addition, is there a better way of capturing the constraint of only a user with the the Labourer can do this action. Is it best to put this check in the location I have put it? Is it best done in the Box class? Do you have thoughts on this?

4 Answers 4

2

I'd be wary about coupling the User's domain and the Box's domain.

Obviously its just an example, so you cant really say, but it seems like in a DDD system you might have a whole lot of things Users are involved in that don't involve boxes.

So instead you could have interfaces to loosen the coupling

Lift(thing: IMoveable) {...}

Or perhaps change the concept of location to be a container

IThing
{
   Id: string
   Type: string
}

User : IContainer
{
    Contents: IThing[]
    Lift(thing, currentContainer) {
       currentContainer.Remove(thing)
       this.Contents.Add(thing)
    }
}

Warehouse: IContainer
{
    Contents: IThing[]
}

Now you can keep track of where things are

Or even reverse the concept, so that objects themselves don't know where they are. Which is probably more "real worldy"

LocationOfThing
{
    Id: string
    Type: string
    position: Vector
}

LocationTracker
{
    Things: LocationOfThing[]
}


User
{
  Lift(thing)
  {
     this.OnLift(thing, this); //locationtracker handles event and updates positions
  }
}
2

My initial reaction was twofold:

  1. Unless there are constraints around moving boxes, the user (or labourer) who moved the box should be metadata. At best, it feels like a LabourerId is all you need.

  2. The constraints for moving boxes are likely not human constraints. It is more likely a business concern — that is what needs to be modeled. Model the business process, not the physical process.

    • Unless the business process is literally a physics simulation.

You haven't specified any more details about the rules, so the following is pure conjecture, but a good exercise in thought. Consider that humans do have constraints. For instance, if you have a kit car in a box (a full-size vehicle fully disassembled because some car hobbiests love building cars). A packaged kit car could weigh 1-2 tons. No human being is going to pick this up and move it. Similarly, a single package of paper napkins would never be moved by a forklift. Consider creating an abstraction for a person or thing used to move boxes. This gives you a place to capture additional constraints, like weight and size restrictions. Some will be physical constraints. Some might be legal restrictions. A person can move boxes, but boxes over a certain weight should require two people (for safety and/or legal reasons). A forklift requires a trained and licensed operator.

I realize I'm taking your simple example to an extreme, but you want to think about the bigger picture of what "moving boxes" really means. Dig deeper to identify the rules governing how, when and who moves boxes. Only once you know these rules can you create the proper abstractions, which might include concrete classes, interfaces or abstract classes.

2

There is no design without context, i.e. a set of requirements that define the context. Even if you understand the business case, there is no point in trying to model the "reality".

Design decisions are based on trade-offs that are defined by the requirements, not "reality", even if there is some overlap. Statements like "this and that should be decoupled" are completely meaningless without knowing this context. And we don't know the context in this case.

I'm not sure whether we are on the same page on this. The "bounded context" terminology in DDD is trying to tell us exactly the above. We are not modeling "reality", we are modeling a small part of it, in which things may mean different things than in other contexts or to a layperson who is just looking at it naively.

In short: "it depends" :) What is the behavior that the system needs to exhibit, regardless of the "reality" outside. Does the Box have other behavior than being lifted? Do Users have other behavior? Do we need to model "labourers" separately? Do the users need a programmatic way to distinguish between roles, or is it "just" an access control mechanism?

These things might decide whether there is a Box in the first place, or whether roles need to be their own type, or where "lifting" would best fit.

0

As in @Ewan answer, it is best to decouple the box domain from the user domain. But to one thing to add is to check out the entity component system design (ECS).

In short, you have the entities user box, that holds components. You write systems that runs and interacts with these components. (This is a very simple explanation. To really write ECS the correct way, some memory management and other things are required. But as a general idea it serves the purpose of this answer).

interface IEntity {
    Components: IComponent[];
}

interface abstract IComponent {
    //Some complicated things related to ECS that doesnt serve the purpose of 
    this answer
}

interface abstract ISystem {
    public abstract void Run(IComponent component)
}

Then you can write the implementations for your example.

public class TransformComponent: IComponent {
    public float positionX;
    public float positionY;

    public void SetPosition(float x, float y){
        positionX = x;
        positionY = y;
    }
}

public class PlayerComponent: IComponent {
    public bool isMovingSomething;
    public IEntity entityBeingMoved; 
}

public class User: IEntity {
    Components = [ PlayerComponent ];
}

public class Box: IEntity {
    Components = [ TransformComponent ];
}

public class PlayerSystem: ISystem {
    public virtual void Run(IComponent component){
        PlayerComponent playerComponent = (PlayerComponent) component;
        //Do the checks to see if the user is moving the box (user is close to 
          the box and holding a certain key for ex)
        playerComponent.isMovingSomething = true;
        playerComponent.entityBeingMoved.Components[(searchForTransformComponent)].setPosition((targetX, targetY)]
            
    }
}

Finally, following the same pattern you can write a render system that loops through all the entities and TransformComponents, and render that entity based on the positionX and positionY. You can add more components used by the rendering system for rendering purposes. Although this whole rendering process is handled by a game engine usually.

Note again: Writing ECS the correct way is much more complicated. For ex, as mentioned the system needs to loop through the components. Some memory stuff is done to loop through efficiently.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.