tl;dr: Keep the core application (“model”, “domain”) code repository separate from the user interface (“controller/view”, “action/responder”) code repository; use a dependency manager in the user-interface repo to bring in the core-application repo.

Nihal Sahu asked on Twitter, “How do you ‘personally’ go about building an application? What Structure?” This post is my longer-than-140-characters response.

Obviously I’m a fan of Action-Domain-Responder as a pattern for web-based user interfaces. (ADR treats the request/response cycle itself as a user interface. The HTML/CSS/JS/etc that goes into the HTTP body is just one part of the presentation half of the user interface; ADR reminds us that the HTTP headers are part of the presentation, too.)

But ADR, and MVC, and the other user-interface patterns – they are not an application architecture in and of themselves. The “Domain” in ADR and the “Model” in MVC are just entry points into the underlying core of the overall application architecture. In a way, the “Action/Responder” and “Controller/View” portions of the UI are not the big deal in the application; the big deal is the underlying core. That’s where the real work happens.

With that in mind, one first thinks the right approach would be to separate the User Interface from the Core Application. This is obvious and uncontroversial; separation of concerns is a central tenet of good architecture.

What might be controversial is my extended advice regarding that approach. I don’t suggest a merely “logical” separation of concerns so that Action/Controller classes don’t have Domain/Model code in them. I suggest a “physical” separation. In other words:

Separate the User Interface repository from the Core Application repository.

Yes, I mean that you should have two code repositories for your application. Let’s explore that idea for a minute.

The first repository, the Core Application one, contains no user-interface code at all. No HTTP, no standard input/output, nothing. It expects to receive input in an interface-independent way, and it delivers output in an interface-independent way. Note that it receives the input. It has to be sent to the Core Application in some way, not read from the interface (no superglobals!). The injected input could be via method parameters, a plain old PHP array, an input object provided by the Core Application, a Command object provided by the Core application, etc. Likewise, the output could be via anything provided by the Core Application, such as a Model object, a Domain Payload, or something else. It is completely independent from any user interface code and can be tested on its own.

The other repository, the User Interface one, contains your “Action/Responder” or “Controller/View” (or “Command/Stdout”) code. There is no business logic at all. All it does it reformat the user input to something the Core Application will recognize, pass it to the Core Application, and receive back some output from the Core Application, which it reformats for presentation to the user.

But if the two repositores are “physically” separated, how can the User Interface get access to the Core Application? Composer becomes your best friend in this case. In the User Interface codebase, add "require": { "project-name/core-application": "dev-develop" } to composer.json . Issue composer update and voila: your Core Application is now available to the User Interface. The User Interface depends on the Core Applicatiion, but the Core Application has no dependency on the User Interface.

What’s great about keeping the repositories fully separated from each other is that it enforces developer discipline. It becomes difficult to put Core Application code in the User Interface code. Any attempts to do so are immediately obvious, and everyone watching can see it happening. It becomes harder to justify to yourself “I’ll add it just this one time” (and thus keeping off that particular slippery slope).

The tradeoff is that you now have to coordinate changes across two code repos. Breaks in the Core Application will break the User Interface. The disconnection can be a positive overall, since you can version the Core Application separately from the User Interface, but in practice it does mean you have to be more attentive to change management.

One more thing about keeping the User Interface “physically” separate from the User Interface in this way is that you start thinking differently about frameworks. I have begun to think that our vocabulary for describing frameworks in PHP land is insufficiently varied. MVC and ADR do not describe “application frameworks” – they might describe User Interface frameworks, but there’s no word for Core Application frameworks. Maybe they need their own separate framework term.