Functional programming with Clojure: why and how does Nubank use it and scale so well?

What are the benefits and advantages that made us choose such a different programming language?

People working together with data

Functional programming is an approach of solving problems with ideas focused on outstanding test coverage, increased code encapsulation, reusability and maintenance of systems. Clojure doesn’t only have an appealing paradigm, it’s also  a consolidated functional programming language, with full interoperability with Java and a nice stack around it.

Furthermore, when we think about scalability, and how fast Nubank had to organize itself and not lose control of the quality of the services being created, a well defined and top standard architecture, to be used in conjunction with Clojure, was extremely helpful to guarantee the success of the decision of the language and the design of a culture to deliver code in a more frequent and reliable way.

What is Functional Programming?

Functional programming is a style of programming that follows the Functional Paradigm, and, for those who don’t know, paradigms are approaches to solving problems, and what differentiates them is how they idealize the solution for a problem.

Concepts and guidelines of functional programming

Immutability 

This is the concept of not modifying any variable during execution, in a way that we only have variables that are read-only, so once you create a variable with an unmodifiable value, if you need to use it, a new one might be created using the value of the other plus the other inputs that you want to give.

  • Advantage: You know exactly what the variable is at any moment of the execution, so debugging gets easier.

Immutability is the key idea of functional programming

First-Class Functions

This is the idea that a function in this language doesn’t have restrictions or limitations and might be treated as a variable.

  • Advantages: Can be assigned to regular variables, might be passed as arguments or return of functions, and can be included in any data structure.

Pure Functions

Those are functions in which you only have a single possible result based on an input, so you are always able to make a prediction. For instance, in the mathematical function “x + 2 = y”, if “x” is 2, “y” is always going to be 4, and this is exactly what a pure function is.

  • Advantage: You have full control over testing and guaranteeing that what you want is happening.

Functions Composition 

As the name says, you may use composition of functions to get a desired result as you wish.

  • Advantage: Isolate logics in many small functions, which increases the facility to test and decoupled logics.

Expressions 

Functions should always return something. Functional programming doesn’t approve the creation of statement functions, which have void results, with procedures that are truly hard to control and predict what is happening inside it.

  • Advantage: Improve test coverage.

Recursions

Over Loops, so iteration is done based on the principle that we don’t have mutable variables and recursion will use the last result to apply the following one without breaking the idea, and also avoiding side effects that are quite common in loop interactions.

  • Advantage: More control over the code, as we don’t lose control of what is done in each iteration of a loop. Reduction of side effects.

Why did Nubank choose to use Clojure as its main programming language?

When Nubank was founded, we needed a powerful language to help us build the best financial technology application, and we wanted it to be the best in terms of quality, consistency and velocity of development. And all those ideas were reflected in Clojure. There were a few things that we evaluated as advantages:

Objective language

Clojure is direct to the point and very succinct to create complex scenarios, qualities that make it a totally non-verbose language. This allowed us to code more in less time.

Outstanding test coverage! Quality Assurance teams were not necessary.

Test while we code

We could have pre configured infrastructure to run the tests at the same time we were modifying it, with almost 100% test coverage of unit testing plus integration test. And all that guaranteed the quality of the service and of what had been done, so we didn’t need to have more people to validate those scenarios for us. 

Java interoperability

For those that don’t know clojure pretty well, the language is built on top of the JVM (Java Virtual Machine) so we are able to use everything in that environment, such as Java libraries, frameworks or any other implementation with Java. Nevertheless, it’s a benefit to have this vast technology available if we need it.

Amazing community and quality documentation

Clojure has a great community space, in which people are engaged in discussing and evolving the language, so it’s really easy for you to have a start and find out wherever you need to learn about it. Moreover, the people that were at Nubank when everything was just a mirage had a great involvement and affection with Clojure, so  it was an easy decision to make. 

How have Nubank scaled so well using Clojure?

Nubank has grown extraordinarily in the past years and the evolution of our consistent growth in the engineering part is due to the organization and technical architecture that smoothened this growth process.

Microservices architecture with a well defined software architecture.

Today, Nubank’s microservices architecture has around 1000 microservices written in Clojure. Moreover, all of the microservice have the same folder structure based in our defined software architecture, the Diplomat Architecture. So, it’s easy to understand how the microservices work and where to find things. All that said, this makes the scalability easier, as we might move engineers over teams, and they only need to learn business context and how this is organized in the microservices architecture, nevertheless the services themselves are straightforward and very easy to manipulate. 

Furthermore, we can increase or decrease the teams according to the priorities, without worrying about engineer tracking or onboarding time.

Having code conventions, software architecture, testing culture and a great infrastructure already prepared to accept all of it allows teams and companies to grow in a more optimized way, focusing much more on problem solving than in keeping things running based on people.