The software development team that I'm currently working with has been looking at a company's software systems, trying to determine the best way for the software base and its applications' capabilities to grow. We decided that a move towards a more 'micro-service' oriented architecture could really help to focus the development teams' efforts as well as help pull into focus the core components of the businesses and the business logic surrounding them.
Toward this goal, I set out to create a model of what I hoped these services would look like by building a service that encapsulated a very small number of components and logic. In creating the project, I noticed a number of places where I was (already) repeating myself. Not only do I feel as though this is against best coding practices(keep it DRY, not WET, but quite frankly I get bored when I need to write the same code over and over again. I like to think that a large part of the software industry is built upon how automation of rote, boring tasks can improve our lives. When I find my code repeating itself, I generally find that there is a way for me to 'automate' it through code extraction. Obviously this promotes code reuse and hopefully helps create more modular, readable, understandable code which also promotes maintainability.
The code-base that currently exists in this company is plagued by a number of inconsistencies and inaccuracies. As one would guess in most long running software systems, we have found a number of issues with the data and the business logic. There are no unit tests and the code has been written in a way that makes it virtually impossible to start wrapping it with unit tests. There was definitely a (small) attempt to create some boundaries for different areas/purposes of the code-base, but as I've frequently seen, when there is only one large code-base, those artificial boundaries are often crossed as a shortcut. There are places where the UI is directly reaching out to the database and the business logic for any given component can be found sprinkled throughout the UI, 'middle layer' and back-end. There are also a number of places where UI is being rendered in or stored by the database.
While keeping all of the above in mind, as I started embarking on the adventure of creating this demo project, I kept an eye on the areas I felt as though could be improved upon through a bit of refactoring. I then took each subproject on its own, started refactoring it and created a number of base classes and interfaces that I thought would provide the foundation for each micro-service as well as provide an example on how to utilize these building blocks. I put the solution on GitHub and will be referring to it throughout this series of blog posts as I walk through the branches and discuss some of the decisions I made throughout.
The first branch I created was the 'model' solution that I hoped to use to demonstrate the project layout I felt most cleanly delineated the intent of each service. The general structure decided upon for each of our 'micro-service' solutions is as follows:
- Apis.csproj: This project is the website that will actually expose the service through WebApi2 Controllers. Acting as the BLL, it also contains all of the validation logic for the resource(s) located at its endpoint.
- Clients.Apis.csproj: This project is a small project that wraps the Api project and allows us(through NuGet) to utilize a strongly typed client for communicating with the component's api.
- Contracts.csproj: This project contains the objects and interfaces that are exposed externally from this Api.
- DataAccess.csproj: This project contains all of the interactions that should take place between this Api and its associated data-store.
- DataAccess.Contracts.csproj: This project contains all of the objects and interfaces that are exposed externally from the DataAccess project. These are only used in the UnitTests and Apis projects. It creates a clear segregation though so that as long as the underlying DataAccess layer adheres to the same contracts, the implementation can change with little or no code changes to the Apis project.
- IntegrationTests/IntegrationTests.csproj: This project contains automated tests related to this BusinessComponents solution. This suite of tests is responsible for setting up actual data in a data-store, calling the Api exposed on a server in relation to that data, validating its persistence in the data-store and validating the ability of said data to be retrieved through the Api.
- UnitTests/UnitTests.csproj: This project contains automated tests related to this BusinessComponents solution. This suite of tests test only one layer(Api, or DataAccess) at a time and mocks out any other external dependencies(Database or Api) to isolate the functionality under test.
As one can see looking through the solution at this point, I really didn't do anything that reused code. Part of this was to be able to contrast at the end how much smaller the code-base is. Another reason is to demonstrate that we as developers are(or should be) always trying to find the right balance between effectiveness and efficiency. The code in this state is effective(it works) and could be considered efficient(it was easy to crank out, once one 'object' was done throughout the layers, the other was accomplished mostly through copy and pasting with a little tweaking).
As you will notice though, only one of object's full stack has tests around it. This is largely due to the fact that they were monotonous and boring to create. There was a lot of repetition and I wasn't interested in doing that all over again for the other object. If the purpose of this code was solely intended as a sandbox to demonstrate the power and ability to weave together a number of components in this fashion or was for a production application that was very limited in its scope or user base, then it might very well be the best place to end the project. It is possible that no tests would even be necessary if either of the above scenarios were true.
Seeing as the intention was to utilize this type of solution to create building blocks for a much more robust and long lasting system though, I wanted to demonstrate some of the traps that can be stumbled upon if one is unwilling to take a little less efficient approach at the moment for a more efficient and effective long term solution. The code is in a great starting place and I'm looking forward to, in the coming posts, documenting and discussing the design decisions I made while refactoring this solution.