Should Domain Model Classes always depend on primitives?
Asked Answered
A

3

19

Halfway through Architecture Patterns with Python, I have two questions about how should the Domain Model Classes be structured and instantiated. Assume on my Domain Model I have the class DepthMap:

class DepthMap:
    def __init__(self, map: np.ndarray):
        self.map = map

According to what I understood from the book, this class is not correct since it depends on Numpy, and it should depend only on Python primitives, hence the question: Should Domain Model classes rely only on Python primitives, or is there an exception?

Assuming the answer to the previous question is that classes should solely depend on primitives, what would the correct way create a DepthMap from a Numpy array be? Assume now I have more formats from where I can make a DepthMap object.

class DepthMap:
    def __init__(self, map: List):
        self.map = map
    
    @classmethod
    def from_numpy(cls, map: np.ndarray):
        return cls(map.tolist())

    @classmethod
    def from_str(cls, map: str):
        return cls([float(i) for i in s.split(',')])

or a factory:

class DepthMapFactory:
    @staticmethod
    def from_numpy(map: np.ndarray):
        return DepthMap(map.tolist())

    @staticmethod
    def from_str(map: str):
        return DepthMap([float(i) for i in s.split(',')])

I think even the Repository Pattern, which they go through in the book, could fit in here:

class StrRepository:
    def get(map: str):
        return DepthMap([float(i) for i in s.split(',')])

class NumpyRepository:
    def get(map: np.ndarray):
        return DepthMap(map.tolist())

The second question: When creating a Domain Model Object from different sources, what is the correct approach?

Note: My background is not software; hence some OOP concepts may be incorrect. Instead of downvoting, please comment and let me know how to improve the question.

Aparejo answered 9/11, 2020 at 14:18 Comment(0)
M
16

I wrote the book, so I can at least have a go at answering your question.

You can use things other than primitives (str, int, boolean etc) in your domain model. Generally, although we couldn't show it in the book, your model classes will contain whole hierarchies of objects.

What you want to avoid is your technical implementation leaking into your code in a way that makes it hard to express your intent. It would probably be inappropriate to pass instances of Numpy arrays around your codebase, unless your domain is Numpy. We're trying to make code easier to read and test by separating the interesting stuff from the glue.

To that end, it's fine for you to have a DepthMap class that exposes some behaviour, and happens to have a Numpy array as its internal storage. That's not any different to you using any other data structure from a library.

If you've got data as a flat file or something, and there is complex logic involved in creating the Numpy array, then I think a Factory is appropriate. That way you can keep the boring, ugly code for producing a DepthMap at the edge of your system, and out of your model.

If creating a DepthMap from a string is really a one-liner, then a classmethod is probably better because it's easier to find and understand.

Manouch answered 10/11, 2020 at 12:40 Comment(0)
Z
8

I think it's perfectly fine to depend on librairies that are pure language extensions or else you will just end up with having to define tons of "interface contracts" (Python doesn't have interfaces as a language construct -- but those can be conceptual) to abstract away these data structures and in the end those newly introduced contracts will probably be poor abstractions anyway and just result in additional complexity.

That means your domain objects can generally depend on these pure types. On the other hand I also think these types should be considered as language "primitives" (native may be more accurate) just like datetime and that you'd want to avoid primitive obsession.

In other words, DepthMap which is a domain concept is allowed to depend on Numpy for it's construction (no abstraction necessary here), but Numpy shouldn't necessarily be allowed to flow deep into the domain (unless it's the appropriate abstraction).

Or in pseudo-code, this could be bad:

someOperation(Numpy: depthMap);

Where this may be better:

class DepthMap(Numpy: data);
someOperation(DepthMap depthMap);

And regarding the second question, from a DDD perspective if the DepthMap class has a Numpy array as it's internal structure but has to be constructed from other sources (string or list for example) would the best approach be a repository pattern? Or is this just for handling databases and a Factory is a better approach?

The Repository pattern is exclusively for storage/retrieval so it wouldn't be appropriate. Now, you may have a factory method directly on DepthMap that accepts a Numpy or you may have a dedicated factory. If you want to decouple DepthMap from Numpy then it could make sense to introduce a dedicated factory, but it seems unnecessary here at first glance.

Zuniga answered 9/11, 2020 at 14:58 Comment(2)
FWIW, I think this answer is spot on, but I didn't have enough reputation to comment and answer the follow up, so had to post a new one. This should be the accepted answer imho.Manouch
@bobgregory Thanks! Your answer is just as good so don't feel bad stealing my reputation with your notoriety haha :). Besides, it's always great to see authors taking time to contribute to other knowledge streams such as SO. Cheers!Zuniga
D
3

Should Domain Model classes rely only on Python primitives

Speaking purely from a domain-driven-design perspective, there's absolutely no reason that this should be true

  • Your domain dynamics are normally going to be described using the language of your domain, ie the manipulation of ENTITIES and VALUE OBJECTS (Evans, 2003) that are facades that place domain semantics on top of your data structures.

  • The underlying data structures, behind the facades, are whatever you need to get the job done.

There is nothing in domain driven design requiring that you forsake a well-tested off the shelf implementation of a highly optimized Bazzlefraz and instead write your own from scratch.

Part of the point of domain driven design is that we want to be making our investment into the code that helps the business, not the plumbing.

Deli answered 9/11, 2020 at 15:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.