Search code examples
clean-architecture

What are reciprocal polymorphic boundary interfaces?


I am currently reading Clean Architecture by Uncle Bob, and there's a discussion in Chapter 24 (Partial Boundaries) about reciprocal polymorphic boundary interfaces. To provide more context, Uncle Bob says that a boundary is used to "separate software elements from one another, and restrict those on one side from knowing about those on the other".

I do understand what polymorphic interfaces are, however, I do not know about reciprocal interfaces. What are reciprocal interfaces in this context?

I've tried searching the web for an answer, however, I couldn't find any discussion on the matter.


Solution

  • I do understand what polymorphic interfaces are, however, I do not know about reciprocal interfaces. What are reciprocal interfaces in this context?

    I guess what he wants to say is that if you want to completely separate two components - a full-fledged architectural boundary, it means that dependencies should not point directly to the other component in both directions. So you have to introduce interfaces, data structures on both sides.

                                │ │                                │ │                           
               ┌──────────┐     │ │      ┌───────────────────┐     │ │     ┌───────────────────┐ 
               │          │     │ │      │                   │     │ │     │                   │ 
               │ Client 1 ├─────┼─┼────► │ Sercice2 Boundary │◄────┼─┼─────┤ Service2 Impl     │ 
               │          │     │ │      │                   │     │ │     │                   │ 
               └──────────┘     │ │      └───────────────────┘     │ │     └───────────────────┘ 
                                │ │                                │ │                           
      ┌───────────────────┐     │ │      ┌───────────────────┐     │ │     ┌──────────┐          
      │                   │     │ │      │                   │     │ │     │          │          
      │ Service1 Impl     ├─────┼─┼─────►│ Sercice1 Boundary │◄────┼─┼─────┤ Client 2 │          
      │                   │     │ │      │                   │     │ │     │          │          
      └───────────────────┘     │ │      └───────────────────┘     │ │     └──────────┘          
                                                                                                                                                           
                                                                                             
    

    If you do that you have to answer the question: Where should I place the boundard interfaces?

    The answer to this question you have to think about the deployment structure. Which components should be released and deployed together. But deployment highly depends on the language you use. E.g. in Java the JAR files are a deployment unit. I use a Java example here but you can adapt it to many languages.

    An interface is more bound to a client then to a provider, since it declares what the client needs. So you might want to put the interface in the same module (JAR file in Java) then the client. If you do that the providers depend on that module. Since the module also contains the client the provider also depends on everything the client needs.

      ┌────────────┐                   ┌────────────┐        
      │module1.jar │                   │module2.jar │        
      ├────────────┴──────┐            ├────────────┴──────┐ 
      │                   │            │                   │ 
      │ ┌──────────┐      │            │                   │ 
      │ │          │      │            │                   │ 
      │ │ Client 1 │      │            │                   │ 
      │ │          │      │            │                   │ 
      │ └────┬─────┘      │            │                   │ 
      │      │            │            │                   │ 
      │      │            │◄───────────┤                   │ 
      │      ▼            │            │                   │ 
      │ ┌─────────────┐   │            │ ┌─────────────┐   │ 
      │ │             │   │            │ │             │   │ 
      │ │ SomeService │   │            │ │ Service Impl│   │ 
      │ │             │   │            │ │             │   │ 
      │ └─────────────┘   │            │ └─────────────┘   │ 
      │                   │            │                   │ 
      └───────────────────┘            └───────────────────┘ 
                                                        
    

    So if you want a full isolation you need to introduce a new deployment unit that only contains the interface and also the data structures the interface needs.

      ┌────────────┐               ┌────────────┐                ┌────────────┐       
      │client.jar  │               │spi.jar     │                │provider.jar│       
      ├────────────┴──────┐        ├────────────┴──────┐         ├────────────┴──────┐
      │                   │        │                   │         │                   │
      │ ┌──────────┐      │        │  ┌─────────────┐  │         │ ┌─────────────┐   │
      │ │          │      │        │  │             │  │         │ │             │   │
      │ │ Client 1 │      ├───────►│  │ SomeService │  │◄────────┤ │ Service Impl│   │
      │ │          │      │        │  │             │  │         │ │             │   │
      │ └──────────┘      │        │  └─────────────┘  │         │ └─────────────┘   │
      │                   │        │                   │         │                   │
      └───────────────────┘        └───────────────────┘         └───────────────────┘
                                                                                      
    

    But if you do that, you also need to build and release the spi.jar, manage versions, an own directory structure in your project and so on. If you use Java with Maven your structure might look like this:

    project/
    ├─ client/
    │  ├─ src/
    │  │  ├─ main/
    │  │  │  ├─ java/
    │  ├─ pom.xml
    │ 
    ├─ spi/
    │  ├─ src/
    │  │  ├─ main/
    │  │  │  ├─ java/
    │  ├─ pom.xml
    │ 
    ├─ provider/
    │  ├─ src/
    │  │  ├─ main/
    │  │  │  ├─ java/
    │  ├─ pom.xml
    │
    ├─ pom.xml                
    

    What Uncle Bob says is that

    ... the dependency management necessary to isolate the two sides into independently compilable and deployable components ... takes a lot of work. It's also a lot of work to maintain.

    You can also put everything into one module (deployment unit). But this opens the door to bypass the interfaces and therefore break the decoupling. Uncle Bob also mentioned this later when he speaks about his FitNesse project:

    Over time, as it became clear that there would never be a need for a separate web component, the separation between the web component and the wiki component began to weaken. Dependencies started to cross the line in the wrong direction.

    A side note about dynamic languages

    In dynamic languages like Python you don't need to use interfaces or abstract classes like in Java to do polymorphism. Therefore the deployment unit problem I described above will not arise. I guess this is one aspect why dynamic languages are popular. But even in this languages you still need to ensure that dependencies do not cross boundaries in the wrong direction.