16
\$\begingroup\$

enter image description here

You can call it ports and adapters or hexagonal architecture. Regardless, what's facinated me about this picture isn't the layers. It's the Clean Architecture UML diagram over in the corner of a plugin that looks like an upside down, folded over version of the Dependency Inversion Principle:

enter image description here

So while the original DIP only showed how to descend into layers this shows how to come back out without creating dependencies that point in the wrong direction.

If I were to color this to follow the clean architecture diagram it would look like this:

enter image description here

Some like to think of an interface like it's owned by the class that implements it.

Some like to think of an interface like it's owned by the client that uses it.

For the first time I'm starting to think either can be true. Both interfaces are really owned by the red inner layer. Rather than ownership of interfaces happening vertically or horizontally here it's happening diagonally. At least until you rotate around the "mechanism Layer" to get back to the clean architecture UML. Then ownership is vertical.

By ownership I mean what gets to dictate change. Neither Presenter or Controller designers get to dictate that the Input or Output port interfaces need to change. The Interactor designer gets to make that call. Which is why browser plugins are at risk of breaking every time a new version of the browser comes out.


I've written some example code that is just supposed to show how these layer plugins might look. Please check it for clarity. It's just a button push without much transformation or useful indirection so it might not be the best example yet. Ideas to improve that would be most welcome. One thing that's completely missing is an Entity.

Code listing presented in an order that follows the flow of control:

package candiedOrange.plugin.adapters;

import candiedOrange.plugin.usecases.ButtonUseCaseInputPort;

public class ButtonControler {
    ButtonUseCaseInputPort button;

    public ButtonControler(ButtonUseCaseInputPort button) {
        this.button = button;
    }

    public void push() {
        button.push();
    }
}

 

package candiedOrange.plugin.usecases;

public interface ButtonUseCaseInputPort {
    void push();
}

 

package candiedOrange.plugin.usecases;

public class ButtonPushUseCaseInteractor implements ButtonUseCaseInputPort {
    ButtonUseCaseOutputPort outputPort;

    public ButtonPushUseCaseInteractor(ButtonUseCaseOutputPort outputPort){
        this.outputPort = outputPort;
    }

    @Override
    public void push() {
        outputPort.push();
    }
}

 

package candiedOrange.plugin.usecases;

public interface ButtonUseCaseOutputPort {
    void push();
}

 

package candiedOrange.plugin.adapters;

import candiedOrange.plugin.usecases.ButtonUseCaseOutputPort;

public class ButtonPresenter implements ButtonUseCaseOutputPort{

    @Override
    public void push() {
        System.out.print("push");
    }
}

Testing each layer seperately:

TestUseCases

package candiedOrange.plugin;

import candiedOrange.plugin.usecases.ButtonPushUseCaseInteractor;
import candiedOrange.plugin.usecases.ButtonUseCaseInputPort;
import candiedOrange.plugin.usecases.ButtonUseCaseOutputPort;
import org.junit.Test;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

public class TestUseCases {

    public static class PushMock implements ButtonUseCaseInputPort, ButtonUseCaseOutputPort {
        boolean pushed;
        public boolean isPushed() { return pushed; }
        public void push() { this.pushed = true; }
    }

    @Test
    public void testInteractor() {
        PushMock presentorMock = new PushMock();
        assertFalse( presentorMock.isPushed() );
        new ButtonPushUseCaseInteractor(presentorMock).push();
        assertTrue( presentorMock.isPushed() );
    }
}

TestAdapters

    package candiedOrange.plugin;

    import candiedOrange.plugin.adapters.ButtonControler;
    import candiedOrange.plugin.adapters.ButtonPresenter;
    import candiedOrange.plugin.usecases.ButtonPushUseCaseInteractor;
    import candiedOrange.plugin.usecases.ButtonUseCaseInputPort;
    import candiedOrange.plugin.usecases.ButtonUseCaseOutputPort;
    import org.junit.After;
    import org.junit.Before;
    import org.junit.Test;

    import java.io.ByteArrayOutputStream;
    import java.io.PrintStream;

    import static org.junit.Assert.assertEquals;
    import static org.junit.Assert.assertFalse;
    import static org.junit.Assert.assertTrue;

    public class TestAdapters {

        private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();

        private PrintStream oldStdOut;

        @Before
        public void setUpStreams() {
            oldStdOut = System.out;
            System.setOut( new PrintStream(outContent) );
        }

        @After
        public void cleanUpStreams() {
            System.setOut(oldStdOut);
        }

        @Test
        public void testOut() {
            System.out.print("hello");
            assertEquals( "hello", outContent.toString() );
        }

        @Test
        public void testPresenter() {
            outContent.reset();
            new ButtonPresenter().push();
            assertEquals( "push", outContent.toString() );
        }

        @Test
        public void testControler() {
            TestUseCases.PushMock interactorMock = new TestUseCases.PushMock();
            assertFalse( interactorMock.isPushed() );
            new ButtonControler(interactorMock).push();
            assertTrue( interactorMock.isPushed() );
        }

        @Test
        public void testEndToEnd() {
            outContent.reset();
            new ButtonControler(
                new ButtonPushUseCaseInteractor(
                    new ButtonPresenter()
                )
            ).push();
            assertEquals( "push", outContent.toString() );
        }
    }

Package structure:

enter image description here

The adapter package is the green layer with controllers, presenters and gateways. You might notice the only thing importing the adapters package was TestAdapters. This ensures that layer can be removed and replaced easily.

Looking to make the above into a readable, robust, and useful example. Critical input welcome.

\$\endgroup\$
6
\$\begingroup\$

Some like to think of an interface like it's owned by the class that implements it.

Some like to think of an interface like it's owned by the client that uses it.

For the first time I'm starting to think either can be true.

It is true. The difference lies in semantics.

Interface owned by its own class

The core is: EVERY class has an interface aside the language construct "interface". The problem is: Sometimes it is not beneficial.

If you have a class in JAVA sometimes it is hard to ensure encapsulation. Therefore you hide the class behind an "interface" language construct.

The separate interface of a class is only an extraction of the interface the class already implicitly has. You can compare it to the header file in the programming language "C" where it provides perfect encapsulation.

In JAVA it is a helper construct which is not enforced. So nothing hinders you to use the concrete class implementation instead of the interface. In "C" you only make the header files known to other compilation units (I hope so, as I am not a C-developer).

Interface owned by the framework

The other way around: A framework provides functionality. To let your object cooperate with the framework they have to meet some requirements that are formulated as "interface" language constructs.

Here comes the complicated thing:

You may have a class with an extracted alternative interface AND you want to have a contract to work together with a framework. Then you have two interfaces with different semantics to handle.

The clean architecture

If you consequently follow the SOLID principle it will guide you to exact this architecture. You have no chance to miss it.

Your code

In general I do not think that pressing a button is a usecase but I think you have a correct reference implementation of clean architecture.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ You are right about header files in c. You're also making me think I need to be clearer about what I mean by ownership (see edit). As for a button as a usecase, I'm trying to keep the example simple. Would it help if the system did more than just pass on knowledge that the button has been pushed? I have a variation that gives the button on and off states rather then being a momentary push button. \$\endgroup\$ – candied_orange Feb 20 '17 at 14:31
  • \$\begingroup\$ I know that you want to keep the usecase small. Extending the usescase would not change my answer :-). A class always owns an interface. You can extract it into a separate construct "interface" and it will semantically remain in ownership of the class. If you want to use a framework and you want the framework work with your objects then the framework decides about the rules it accepts your objects. You have to meet interface contracts defined by the framework. \$\endgroup\$ – oopexpert Feb 20 '17 at 16:48
  • \$\begingroup\$ I disagree. The owner of an interface isn't about semantics. Just because you name your class Dog doesn't give you authority over the Pet interface that my client uses. \$\endgroup\$ – candied_orange Feb 20 '17 at 16:57
  • \$\begingroup\$ You didn't match my case. Dog to Pet is a semantical (and technical) abstraction. Dog.cpp to Dog.h is separating the interface from the implementation of the exact same semantic. It's only a technical abstraction to hide internals. \$\endgroup\$ – oopexpert Feb 20 '17 at 17:05
  • \$\begingroup\$ Well that's just silly. That means interface ownership can only happen in languages that allow implementations and interfaces to have the same name. No. Ownership is not such a simple problem that the names are all you need to know. \$\endgroup\$ – candied_orange Feb 20 '17 at 17:18
3
\$\begingroup\$

I've been thinking about this idea of "who owns the interface?" recently; hence my finding this post. I think something that might be missing from the conversation is another diagram from Uncle Bob's Clean Architecture.

You can ignore the details of the particular interfaces and classes in the below image, as nobody is required to design systems exactly the way Uncle Bob says. However, the idea this image conveys adds some detail to the onion diagram provided in the question.

Clean Architecture diagram showing dependency coupling such that all dependencies cross boundaries in the same direction, ensuring outer-layer components depend on stable inner-layer components

The idea relates to the Packaging Principles.

Regarding the outer layers of the onion:

  • Components become more concrete as you approach the outer layers of the onion diagram.
  • Concrete packages are unstable and tend to contain mostly concrete components.
  • Concrete packages depend on stable packages/components.

Regarding the inner layers of the onion:

  • Components become more abstract as you approach the inner layers of the onion diagram.
  • Abstract packages tend to contain mostly abstract components.
  • Abstract packages depend on their own abstract interfaces (they own).

Dependencies tend to point inward toward the stable package/components.

The Views section in this diagram, for example, I think would be similar to the Button example given in the question. The Views package is concrete and has stable dependencies on the Use Case Boundaries abstract components. The Views package itself, being concrete, doesn't need interfaces that it owns in order to abstract away the other layers of the application; otherwise, you would need another concrete adapter layer to call the stable abstractions or you could risk the inner layer of the onion depending on the outer layer.

If a view layer uses a library or framework, then the framework will be mostly abstract and talk to interfaces it owns - it has its own stable abstractions - that the Views layer would import and depend on by calling or implementing its abstract types.

So I think who owns the interface depends on how abstract (stable) or concrete (unstable) a group of interacting components are and who depends on them.

| improve this answer | |
\$\endgroup\$

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.