Comments (10)
Hey @SHxKM this is a really well thought out comment and I appreciate the feedback.
First off, I am assuming the second code example has a mistake and it should be artists/interfaces.py
and not albums/interfaces.py
. Because a domain's interface to itself is redundant. I'll go on the basis that this is wrong for the rest of my reply 😄
So you're right - those two examples are identical, and in this instance the are violating the DRY principle. If this was as far as we were going to take this project, that redundancy could be refactored. One of the core principles of DADs (I need a better acronym) is to make it easier for future developers to come in and expand features in place. I think perhaps the example given is not a real-world case. Let me explain.
Let's evolve with the example you gave: Artists
and Users
both want to communicate with Albums
. We create the initial state:
# user/interfaces.py
from album.apis import AlbumAPI
def get_latest_albums():
AlbumAPI.get_latest()
# artists/interfaces.py
from album.apis import AlbumAPI
def get_latest_albums():
AlbumAPI.get_latest()
Awesome. We're violating DRY here, so maybe a developer will choose to not do this and refactor. That's probably okay for that requirement and it'll pass code review (even mine). If we didn't go any further with this, interfaces is redundant. We don't really need DADs.
But thi is a real-world evolving project, so we've suddenly got a new requirement:
- Artists want only the following fields from albums: album title, listens, and sales.
- Users want only the following fields from albums: album titles, and publishing dates. What's more, we want to decorate the album title with some weird string formatting because it's a millennial-esque website and we like fancy things.
- They both want slightly different attribute names for each domain.
Now it should become clear why two interfaces is not redundant. The needs for Artists
and Users
from Albums
is different:
# user/interfaces.py
from album.apis import AlbumAPI
def transform_albums(*, albums):
return [
{'title': f'*~-_-~{a.title}~-_-~', 'published_date': a.publish_date}
for a in albums
]
def get_latest_albums():
albums = AlbumAPI.get_latest()
return transform_albums(albums=albums)
# album/interfaces.py
from album.apis import AlbumAPI
def transform_albums(*, albums):
return [
{'title': a.title, 'listens': a.listen_count, 'sales': a.sales}
for a in albums
]
def get_latest_albums():
albums = AlbumAPI.get_latest()
return transform_albums(albums=albums)
What is the alternative here? We could develop these requirements inside the Albums
domain, but then that domain will start having business logic for other domains within it. From my experience too, this breeds a "your problem" culture, especially when you own Albums, I own Users, and I don't want to have to maintain your stuff.
DDD talks about responsibilities and separating concerns. With DADs, the interfaces layer is that transformation layer that allows domains to remain pure, and the interfaces between them handling any required transformations.
Does this help clear up the reason behind them?
from django-api-domains.
I've pinned this discussion because I think it is useful for others who are keen on the guide but don't know what to do!
from django-api-domains.
@phalt Thanks for the detailed clarification. I have a follow-up question:
so maybe a developer will choose to not do this and refactor. That's probably okay for that requirement and it'll pass code review (even mine). If we didn't go any further with this, interfaces is redundant. We don't really need DADs.
But thi is a real-world evolving project, so we've suddenly got a new requirement:
But what if there really isn't a new requirement? What if this get_albums()
func is really needed as is in say, 3 places? My question then is, where do you (or would you) place it?
Thanks again for this refreshing methodology.
from django-api-domains.
No probs. I'm just keen to have people interested in a thing I developed from all my hard work 😆
It sounds like the interfaces could be promoted into it's own domain, sort out like a gateway or orchestration layer. I mentioned that some domains can just have services, and their only job is to co-ordinate or abstract other domains beneath it. Perhaps that could fulfill the same requirements we have here?
from django-api-domains.
Sounds like a good suggestion :)
from django-api-domains.
But what if there really isn't a new requirement?
If it's not proving useful then it might be overengineering. I'd clean it up. There isn't anything wrong with anticipating future changes and then, when you get to the future, discover there isn't any. Software is fluid and changes. We'll just adapt to what makes sense for the situation currently. If we discover we need to add it, we just put it back in. This is what the "pragmatism" bit means at the start of the guide.
Thanks for your comments, it's helped me challenge my assumptions!
from django-api-domains.
Thanks @phalt. Your positive attitude is quite refreshing.
I wasn’t trying to challenge your assumptions per se, more like trying to pick your brain. So if you do have a general rule of thumb regarding this question, I’d be glad to hear your thoughts:
My question then is, where do you (or would you) place it?
from django-api-domains.
Perhaps that could fulfill the same requirements we have here?
I'm a little hesitant to make a concrete suggestion as I have very little experience with DDD or DADs, but perhaps a reluctantly populated common
module can be introduced in cases where a coordinating layer doesn't really fit in one of the domains in the project? common/interfaces.py
?
from django-api-domains.
Great. Thanks for your responsiveness. Feel free to close this :)
I’m happy to contribute to the docs if you feel that should go somewhere in there but I do feel this is an edge-case, as you noted.
from django-api-domains.
Hey @RTS340 yes I have had issues like that in the past. This isn't a problem with Django API Domains per say - you can end up with them in many ways. Usually circular imports are usually due to design choices in the past create circular dependencies.
I have two suggestions, one easy, one more complex:
- Easy option - put your imports at the top of functions instead of a file - then the import will only be evaluated when ran, and this usually avoids circular dependency issues.
- Refactor and redesign your components to have a 3rd "parent" domain that co-ordinates both.
The first way is easy but inevitably feels hacky, and that's because it is a little bit.
The second way is a much better approach, but costs time and effort - which understandably we don't always have!
from django-api-domains.
Related Issues (20)
- [FEEDBACK] Please, take a look at the dry-python project.
- [QUESTION] How to use serializers from other domains for nested relationships HOT 5
- [QUESTION] Can you provide an example using DRF?
- [FEEDBACK] Dry-Python HOT 3
- [FEEDBACK]Isn't this styleguide encouraging anemic domain models? HOT 2
- [QUESTION] What's the best way to handle errors between apps? HOT 3
- [QUESTION] Restrictions on data types that services may return. HOT 2
- [QUESTION] Model relationships between domains. HOT 3
- [FEEDBACK] Can cover your thoughts on value object? HOT 2
- [FEEDBACK] Clarify with DRF HOT 5
- [QUESTION] How do you handle models that are used in more than 1 domain/app? HOT 4
- [FEEDBACK] Thoughts on foreign key fetching? HOT 1
- [Question] Thoughts on where/how permissions are enforced? HOT 2
- [FEEDBACK] What about rich models? HOT 2
- [QUESTION] Models required across multiple domains HOT 1
- What's next? HOT 9
- [FEEDBACK] - Domain Rules numbered list issue
- [FEEDBACK] Example needs more than one domain HOT 2
- [FEEDBACK] Change "All data returned from APIs must be JSON serializable." HOT 2
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from django-api-domains.