Nette business service
One of the ways to implement model in Nette (and surely not the only one) is to extensively use its Dependency Injection Container (DIC). Each and every model access point can be registered in the DIC as a service and later injected by the DIC to any presenter. These services range in size from simple CRUD models (where no controller is needed) to a business service as controller over Doctrine 2 model.
However the most interesting implementation of this service for our purposes is when the service implements a 3rd party API. In this case our implementation of the service is agnostic to the language the API backend is written. More often then not, we don't actually know in what language the API backend is written. And we don't really care as long as the interface is sufficiently language agnostic. Both SOAP and REST fits the bill, since both REST and SOAP APIs can be accessed by virtually any programming language.
Another, more concrete example, is the use of ElasticSearch as a CRUD model. In this simple casw there is no need for a controller, since there is no business logic. Therefore the way we call the model is essentially the way we could call the whole Controller-Model stack. In the presenter we request a service with Elastica connection and create a request to the ElasticSearch REST JSON API. Therefore the presenter calls an API to access the Controller-Model. But calling an internal API in your own application is a design that is already in use. It is called Service Oriented Architecture (SOA) and the applications behind the API are called microServices.
mSVP – microService-View-Presenter
With this reasoning we get to a design that will support any number of applications in any programming languages for the PV stacks:
mSPV design architeture
With this design, there are no business services in Nette, but rather services that provide a connection to the microServices. For any PV application, there is no dependency on the model, there is not even any knowledge of the model, but rather ability to connect to the microService API is required.
This architecture does not mean you will not have any models in your web application. There is need for news, annoucments, etc. All of those do not need to be accessed by anything else other than the website and therefore do not have to be decoupled into a microService. However you should have your CM stack decoupled into a business service. That way it is always prepared to become a microService.
The obvious question is: How to get there? What are the steps to decouple my application to achieve this design? Generally, you can follow these rules.
- Introduce a presenter – a single point in your application that handles the incoming request and sends the response. At the beginning it fill feel like a „responsible for everything“ code. That is OK. You will refactor it later. For PHP it is the access point where superglobals are called and anything is send to the output.
- (optional) Introduce a view – decouple the response logic into its own separated classes (or functions). The presenter should gather all the data that are required for display and pass them to the view that will format them.
- Introduce a model – refactor your presenter once again. This time, find all the calls to the storage and hide them behind another interface. All the sorting, searches, conditions, all should be hidden behing the model. At this point, some of your CRUD models will be a business service.
- Introduce a controller – refactor your presenter. Find all the places where there is any business logic, that is independent of the type of request recieved. (by type I mean WEB, API or CLI). Get all of this logic into a seperate controller. The presenter then will only format the request according to the controller interface. At this point, you have fully fledged business services.
- Get a language agnostic API between your presenter and controller – Up until this point, your presenters and your controller are probably written in the same languages. You also call the controller functions/methods from the presenter directly. This makes it impossible for applications written in other languages to connect to the same business service. Create an API for your controller and write a service for your presenter that will call that API.
You can skip some of the earlier steps based on the state of your application.
Disadvantages and Benefits
As with any design, there are some benefits that it bring, but also some disadvantages. The benefits I came up with:
- Code is well decoupled – more programmers can work on it at once and they have to use less mental capacity (since there is less code to focus on at once).
- Testability – the code is much easier to properly and fully test, since the code is well decoupled
- Scalability – Your web-server is overloaded, but your model is fine? Just add more PV stacks and put a load balancer before them.
- Asynchorous/Parallel run – By the same token of scalability, you can have several instances of your microService running and handling the Presenter requests. Then you need to worry about concurrent access to the model. Or the Presenter can call several microServices at once, if the call are independent from one another.
- Freedom of choice – You can use the best tool for evey component. Being it a different programming language or just a different framework, you are free to choose the best tool on the market.
And possible disadvantages:
- Freedom of choice – But this is also a disadvantage. If you use too many tools, write your application in many different languages, you will have problems with finding the developers for all the different parts. Developers skilled in all the languages and all the tools. This problem is partially mitigated with increasing size of the team, as there is higher chance someone (or ideally more tha one person) will have the skill you need for the particular component.
- Code overhead – There is always some amount of code overhead with decoupling, in this instance mainly with the API between Presenter and microService. This can be mitigated by using a appropriate library to implment massive chunks of this code.
- microService Latency – there is an added latency in execution as the microservice is called as opposed to direct call from Presenter to Controller. While this cannot be mitigated, it is usually outshined by the runtime saved by the Scalability and Asynchorous/Parallel run benefits.
- Managment – having more separate applications and services means more managment, logging and potencionally more servers (at least virtual). This can be a huge overhead for small applications and for companies without dedicated Ops team.
- Development – While there is not much problem with the development of the PV stacks (and the easy development and concurrent use of several versions of API PV stack is a huge benefit), it could be substantially more difficult to change the API signature of the microService. The solutions for this problem are plenty and just a Google away.