September 27, 2016
Doing your job as a software developer is not limited to writing code and attending meetings. It should also include keeping in sync with your company’s technical strategy. It may not be that obvious, but a developer should know what the technical strategy of the product is, why it needs a plan at all and why she should care.
In this article, you will find out about being a developer in a company that has a sound technical strategy.
First, let’s review why having it is so important:
Set direction, priorities, focus, purpose
Working hard is not the point. What matters is putting work into the areas that are most important. Doing that will have the most impact towards the success of your product, of your business.
Get everyone on the same page
It’s not enough to build things right. You also need to make the right things. And to do that, everyone has to know what needs to be done, why it needs to be done and who should be doing it.
Having a strategy and a plan can make things easier for people involved in a project. When there are a thousand ways of doing the same thing, it’s helpful to lean on something that can guide you towards making the right decision, based on purpose and priorities.
The strategy puzzle – how we do it
Next, we’ll go through the details of each component of our technical strategy. The descriptions include accounts on how these things affect me as a developer working in this environment.
Talking about the software architecture means looking at the bigger picture. Decisions on architecture are taken at the very beginning of the project. They affect the project in the long term. That’s why this kind of decisions need to be taken carefully into consideration.
Although it is not exhaustive, here is a list of typical questions being asked at the beginning of the project:
- What programming language do we choose?
- Are we going to use an object-oriented paradigm, a purely functional one, or a mixture of both?
- Is the database going to be event sourced or a classic global mutable state?
- Will reactive programming play a part in the system? How about microservices?
- Are we going to need a rule-based engine? etc.
In our case, we went for a monolithic web application written in Groovy and Grails, storing the data in a MySQL relational database. We chose this in order to stay away from verbosity, for the dynamic nature of the language, for the “convention over configuration” approach of the framework, and for the support that comes with the widespread adoption of the open-source database.
When it comes to software design, things are less rigid: throughout the lifetime of the project, the design will be much easier to change than architecture. Design decisions are taken on a lower level and they concern abstraction layers, software components, contract interfaces, domain entities, and so on.
Apart from the concepts introduced by default by the Grails framework, such as controller and service layers, command objects and domain entities, there are other patterns we identified after several iterations of the project. These models have taken shape over the course of weeks of software design improvements, and they include but are not limited to:
- view models that just carry data from the controller to the view layer
- domain to view model mappers
- database services that perform updates on the repository
- database queries that simply fetch data from the repository
When combined, the design elements described above provide a skeleton that covers all the requirements of a typical request-response flow. It is relatively easy for a new developer to implement a new feature, given she has understood the purpose of each design element, and how they relate to each other.
There is no document to be followed blindly and religiously. There are no rules set in stone to be enforced. No one is being punished for not applying the Almighty Guidelines. However, we do set expectations by discussing “do’s and dont’s” during the code review sessions, the monthly lightning talks and the code katas we perform together.
At the end of the day, the code must be written in such a way that we can answer with a resounding “Yes” to the following questions: Is it usable? Is it readable? Can it be easily changed?
Our testing strategy includes writing test suites that are executed on demand, or automatically before a new version is deployed into production. These suites serve as the entry criteria of a new deployment into production. The types of tests we employ are:
Unit tests – are used to validate the behavior of individual classes in isolation, which means that its collaborators are being mocked
Integration tests – are used to check the correctness of methods which store and retrieve data from the relational database without mocking it
Functional tests – are used to verify the application as if a real user were using it, by going through the user interface and checking that the correct outputs are being displayed on the screen.
Recently we were hit by the fact that the functional test suite was growing quite big and that the amount of time spent on executing it was soon to become unacceptable. It became evident that we were testing too much and that we were over-relying on this particular suite of tests. That is why we have organized a short session to determine the core functionalities that are critical to the product. Based on that, we have defined a new, slimmer suite of functional tests to replace the old one.
On top of the unit, integration, and functional tests, we also make use of manual exploratory testing and some smoke tests after each new deployment. All of the above give us the confidence that we delivered the right thing and that it functions properly.
Our implementation process setup consists of a continuous integration server connected to the source control repository. Whenever a change is pushed to the master branch, the application is built, tested and deployed into the production environment.
Such a setup has the advantage of being configured once at the beginning of the project and then “forgotten.” Every time a new commit is pushed, it will churn out a new build, on its own without manual intervention. It is needless to say that a lot of time is being saved by not doing manual deployments. Our process is less error-prone and repeatable.
Unlike in a traditional iteration-based agile process, we do not release once every two weeks, but rather every time we have a new feature ready to be deployed. Sometimes, this means several times a day. This approach removes the burden and the fear of deployment from the shoulder of developers and allows us to focus on what matters, which is to deliver value in the shortest cycle time possible.
By now, you’ve taken into account why having a technical strategy pays off in the long term. Maybe the stories of the impact it had on my daily work will inspire you too. At the very least, I hope you’ll give it a try. Let us know in the comments how that worked out.