question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[FEEDBACK] Further clarify the role of interfaces.py

See original GitHub issue

Hi. Thanks for your hard work on this documentation.

Even as a sole-developer wanting to refactor a fairly small Django application (12-15k LOC), I find value at least in the concepts offered by the approach presented here. It has set me on a path to better understand DDD, so I’ve also watched this talk which advocates services.py for writing to the database and selectors.py for reading from the database.

There’s one part that I don’t quite understand yet: the interface part. A few things are puzzling me about it and I hope to get some clarifications (I’d be happy to contribute to the docs if conclude the answer may be helpful to others). I hope I can articulate clearly.

Interfaces and DRY

We are told in the docs that if one domain needs to talk to another, it must do so using an interface that it cultivates. In other words, DomainA that wants to take some info from DomainB must do so strictly through DomainBInterface. But what about DRY?

Let’s assume three domains: Artist, Album, User. (just like in the example docs, it could be argued that Artist and Album should live inside the same domain)

Assume that both Artist and User will want to fetch the same info from our Album, say albums within the last seven days. Does that mean that Artist and User must both write the same Album interface?

Below is some pseudo-code:

# user/interfaces.py
from album.apis import AlbumAPI

def get_latest_albums():
    AlbumAPI.get_latest()
# album/interfaces.py
from album.apis import AlbumAPI

def get_latest_albums():
    AlbumAPI.get_latest()

Doesn’t this produce non-DRY code?

Interfaces and refactoring

Tying with what is brought-up above, it’s still unclear to me why the fact we have interfaces.py saves us headache when refactoring.

If a method in domain_b.apis has to change, then we potential will have to refactor all DomainBInterfaces that were constructed in other domains (domain_a.interfaces.DomainBinterface, domain_c.interfaces.DomainBinterface, etc…).

Whereas if we consume on the DomainBAPI directly, we only need to change the method in one place? I’m sure there’s something I’m missing here.

Issue Analytics

  • State:open
  • Created 4 years ago
  • Comments:10 (6 by maintainers)

github_iconTop GitHub Comments

3reactions
phaltcommented, Aug 14, 2019

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?

2reactions
phaltcommented, Aug 21, 2019

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!

Read more comments on GitHub >

github_iconTop Results From Across the Web

Interfaces - define behavior for multiple types - Microsoft Learn
An interface in C# contains definitions for a group of related functionalities that a non-abstract class or a struct must implement.
Read more >
Implementing an Interface in Python - Real Python
Interfaces play an important role in software engineering. As an application grows, updates and changes to the code base become more difficult to...
Read more >
How to Write User-friendly Command Line Interfaces in Python
In this article, I want to explain the importance of being user-friendly (show your empathy) and how we can create a user-friendly CLI...
Read more >
Interfaces and Inheritance in Java - GeeksforGeeks
Interface inheritance : An Interface can extend other interface. interface_inheritance. Inheritance is inheriting the properties of parent class ...
Read more >
Segmented controls - Human Interface Guidelines - Design
Aim for no more than about five to seven segments in a wide interface and no more ... Consider using introductory text to...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found