Putting the Clean Architecture into practice — part 1

Miguel Loureiro
6 min readApr 16, 2017

How we’ve introduced the Clean Architecture with #golang.

A year ago we’ve decided to introduce a new language at Uniplaces, being Golang the choice. Despite the effort that introducing a new tool is, it can pay off in a lot of ways! It makes you think out of your box, it shows you different ways to solve problems, and mostly, it gives you the flexibility to start fresh again.

Starting fresh

I was super excited for having a new language in the team, it was really about time! We’ve always been quite pragmatic and a bit conservative in growing our stack. While PHP was working for us (still is) we weren’t adding new languages just because there were new cool kids in town. In a fast growing and demanding product startup, you really need to pick your battles. But, since the reasons emerged for that to happen (improve perfomance in our background jobs, faster API’s and a language with good concurrency support) the time to add something new had come.

It was time to start again! Expect team learning curve, build environments, new delivery pipelines, coding standards (Golang helps here), best practices, architecture… And since I like to think ourselves as craftsmen, I really wanted to keep the same code quality we had in PHP and the decision of a good architecture which we would develop on top was of utmost importance.

Picking the Clean Architecture

Just to give some context, Uniplaces was created following Domain Driven Design, which is an approach to design your systems prioritizing your business domain model. Despite this being an architecture that really works, for our needs, something leaner should fit us in some of our upcoming projects, since most of the API’s and jobs we wanted to create weren’t having domain logic. We did like the layered approach and separation of concerns DDD with Hexagonal Architecture was giving us so the Clean Architecture was the way to go.

After a decision on where to go, it was time to write our first service working with this architecture, we will show you our first attempt in making the theory something tangible that works for us. But first, just a glimpse of what we’re talking about if you’re out of context.

These are the names we gave to our layers

The overriding rule that makes this architecture work is The Dependency Rule. This rule says that source code dependencies can only point inwards. Nothing in an inner circle can know anything at all about the implementation of something in an outer circle.- The Clean architecture

Let’s now see an example of how we write a service that gives us information about an Account (being an Account, an Entity), consider the following directory structure.

One book I always like to recommend is Growing Object Oriented Software, Guided by Tests, this book thought me to write software from outside in, define your use cases first and start working on it with tests until you get to implementation details, so let’s first start working in the use case layer.

The code might have some errors didn’t compile it was just writing it for the sake of the article. :)

To get account information is our use case so let’s write a test for it in the use case layer.

Please read the code and comments carefully!

It’s quite cool that after the test is created we know exactly what should be happening, without really knowing how. By thinking in what we want to accomplish we’ve found that we’ll need an object where we’ll hold information about the account, we’ve defined the method we want to call and his arguments, what dependencies will the use case interactor have and what should be returned. Of course, now the test is failing, let’s write the implementation and make it pass.

The test will still fail because we haven’t defined the Account Repository interface and the Account Domain Object, so let’s create it.

We now know, that our use case interfaces with some kind of repository that returns domain objects. So let’s just create an anemic domain object and make the test pass.

The test is now passing and we have our first use case implementation and current directory structure is like the one below. We’ve made a decision of making the repository to return an Account Domain object (this may or may not suit you, we’ll talk about this later), but it still follows the rule. Dependencies should point inwards, since the use case layer is in the outer circle compared to the domain layer, you can refer to something that is in the inner circles, such as the account domain object. Now, looking at our current directory structure.

Note that just by looking at the usecase directory we know that we have repositories as dependencies but I don’t really know about their implementation. But now what? We just have a test passing, this isn’t really delivering value to any user. Where should we get the data from? How should we deliver that data to the end user ? That’s up to you and this is the best part of it! You can work independently by having different people working on different layers, because you have the contracts defined. You can even reconsider your choice on where to store and get data in the middle of your implementation and just replace the implementation of the Account Repository without affecting any code in the other layers.

Let’s now continue our work and we’ve decided that we want to give results to our customers through an API. This means we’ve found our first delivery mechanism, which is, HTTP. In the delivery directory we can have everything related to our REST API and by the way we’ll use the Gin framework.

Again, first write the test (not going deep as above) of the request we should receive and the response we should return here’s the implementation that makes the test pass.

Please read the code carefully and the comments it has to understand what’s happening, this code is our Gin App, we’re just creating it and matching our route with the controller that will handle the request and here’s the current directory structure of our project.

Great we have our Delivery, Use case and Domain layer ready and passing tests, but will this application work if we run it? Will it pass functional tests? It won’t! That’s because we’re still relying on mocks for it to work and we need to implement our Infrastructure layer, and the only thing we need to care about is that the only layer that has an interface for the Infrastructure layer is the Use case layer so let’s revisit it and understand what we need to do by looking at it.

The only thing we need to care about is that we should have matching implementation of this interface, we don’t care if it’s MySQL, Redis, DynamoDB, we just know we should return an Account Domain object. Let’s create a DynamoDB implementation.

This way we’re having a match to our use case interface and we can finally run our application!

Here’s our final directory structure

I hope this can serve as an example on how to put the theory into practice!

Some thoughts I would like to share

When introducing this at Uniplaces I’ve learned a lot, but the most important thing I’ve learned, is that no one outside your context is 100% right. You can spend months reading books and trying to apply the theory you read into practice, but in the end, what I feel it makes someone grow is looking at the “final” result and understand that:

  • No one knows more than you about your team needs
  • You can try to follow precisely the theory but sometimes, you’ll need to step up and be the one understanding the tweaks you need to do in order to make it feasible for your team to work on it
  • The hardest part is not understanding it and applying it, is advocating it and teach others how to do it

Considering these thoughts the result you’re seeing here isn’t exactly how we’re working right now. There were some problems that we faced that will lead to a second article about this topic!

A few of them that we needed to address during this journey:

  • In an increasing service architecture how could we boost the speed of development
  • How can we stop writing the same code at the infrastructure layer in different services that may need the same repositories
  • How can we advocate
  • How a simple name change can completely shift and ease the learning curve of more junior developers
  • How to handle the dependency hell

Hope our experience served you well guys and I’m willing to have time to write the second part of this story!

--

--

Miguel Loureiro

Product & Technology, entrepreneur, early-stage investor and advisor, occasional blogger