The world of software development is exciting and fast-paced. But stepping into it for the first time as a graduate fresh out of varsity, you can easily get overwhelmed — maybe even completely burned out. Having spent the past 7 months figuring out the 1s and 0s of this world, I'd like to share some tips to make this crazy ride a bit smoother.
My Journey: Implementing caching on our repository methods
It was a fine Monday morning and I had just finished a complex ticket. Feeling like I could do anything, I decided my next big task to be the implementation of caching on some of our project's repository methods so that we didn't have to waste precious computation time getting stuff from the database. I would get us "all dem performance gains" before our go-live deadline a short 2 weeks away.
I was going to research caching with Spring Boot- a framework that helps you write java backend applications. Like Dorothy from the Wizard of Oz I was ready to make my way down the Yellow Brick Road, just that I wouldn't meet scarecrows or cowardly lions along the way, but all manners of interesting technologies. I had a plan and a lot of enthusiasm - and that's all I really needed, right? Well, almost.
To implement caching I had to follow a number of steps:
- Learn how caching works in Spring-boot
- Test out caching on a new tiny test project
- Implement caching in our current project
Testing my first application
'Cache abstraction'. The 'cache manager'. 'Concurrent hashmaps'. I heard about all these Spring-boot concepts for the first time but I felt armed with at least a basic understanding of what I needed to do: Use the annotations provided by the Cache Abstraction to tell my application which methods to cache and use the cache manager to confirm if the cache had been populated. This seemed simple enough. That's why I decided it was time for my first test application. I created a:
- Test Controller to make API calls to, and a
- Puppies Repository - a mechanism that usually implements storage, retrieval and search behaviour with a method returning the test response, "All doggos are good boys."
I added a time delay to simulate a slow service and cached the response. A method that initially took 3 seconds to run now sprinted to completion in about 23 milliseconds. I was ecstatic! Coded, it looked like this:
@Cacheable("dogs")
public String getPuppies() {
try { \
long time = 3000L; \
Thread.sleep(time); \
} catch (InterruptedException e) { \
throw new IllegalStateException(e); \
}
return "All doggos are good boys";
}
Implementing caching in our existing repositories
My next challenge was implementing caching for one of the methods in our existing repositories. No puppies and doggos here. This was a complex implementation with many layers, which included:
- Controllers that mapped an API endpoint to a piece of code,
- Services designed around business logic, which also made use of the repositories,
- Repositories implementing how we talk to our MongoDB (a document based Database),
- Custom repositories implementing auxiliary functionality.
I followed the steps I used for my test application and ran my service, trying to contain my excitement to see how much faster the response would be.
"Exception Occurred: Your code did not compile." What? How? Why?
I carefully removed the code I had added for caching and, suddenly, no more exception. Everything worked. But this exception seemed to have nothing to do with caching. That's when my journey through debugging-hell began, which brings me to tip number one:
Tip 1: Learn debugging to find and correct issues quickly
One thing that wasn't given enough attention on campus, at least in my experience, was the art of debugging. To me, debugging meant taking a stab at code in pitch-black darkness while wearing two blindfolds and hoping to find your target.
Debugging is not about randomly tweaking knobs, but rather following the code's execution process, identifying the root of the problem and looking at the bigger picture. Here are some blog posts that helped me understand debugging better:
I now ask myself three questions when debugging:
- "Why and how did this inconsistency/fault appear?"
- "How can I mend it so that it will not occur again?"
- "Will the fix that I'm applying have any side effects?"
All of these help me leave the code in a better state than I have found it.
Your Integrated Development Environment and your browser provide several tools. Find them, understand them, and they'll improve your coding time by over 9,000!
Tip 2: Ask for help. A lot.
My debugging efforts led me to the actual problem: We had certain methods in our repositories that did not belong there and caused the application to crash on run time, but only when caching was involved. That's why no one had really noticed this issue before. -lucky me!
14 hours after kickoff I was still no closer to the solution but I was determined to fix this problem before I went home. The many forums and discussions on Git issue pages hadn't gotten me far. Thankfully, my team members are always walking about discussing problems, ideas and, most importantly, making coffee runs. That's how, in this dire moment of need, a senior developer passed by. He explained how Spring data works with MongoDB under the hood, what functions should go in the repositories and what functions should go in the custom repositories. After a tedious day and a half of moving a whole lot of code around, my application finally ran thanks to that one chat.
As a grad, there are tons of things you don't know but there are usually people with years of experience who probably solved the same problem a couple dozen times.If you're like me, you would want to challenge yourself and try to figure it out on your own.
Trying things on your own first is a good thing but remember to timebox yourself. If you can't solve the problem within 60 minutes, ask for assistance and the solution might be simpler than you thought.
Implementing Hazelcast
A week later, I had learned a ton about caching and created a service to manage the cache and log its contents. It was now up and working in our live system. Win! However, turns out that wasn't the finish line just yet! The default Spring Simple Cache Provider was limited for what we needed. While it works fine for small monolithic applications, we wanted our caching to be performant and able to scale well with our microservices.
That's when my tech leads suggested I implement something called Hazelcast: another foreign concept and a lot more learning to do. In essence, Hazelcast is an in-memory data grid where your cache will be stored so it replaces the default simple cache provider mentioned earlier.
For this, I had to:
- Research and understand Hazelcast
- Implement Hazelcast locally
- Implement Hazelcast on our servers
Let's just say that if life was a role-playing game and I was a level-10 coding wizard, then Hazelcast would be that level-50 demon boss that I had to slay - and that with only a week to go until the deadline! The basic implementation tutorials I found online seemed simple enough, but, alas, they didn't quite apply to our microservice architecture.
Tip 3: Read the documentation! Trust me. Just do it.
Here's what I did: I created instances of Hazelcast per microservice by splitting out the store and making a whole bunch of stores per microservice. Talking to my tech lead after I was done helped me realise that the way I had built things was not the way Hazelcast was intended to be used. This led me to what turned out to be a golden realisation: read the documentation!
While it was tedious and I had to administer trusty old Google's help for a lot of things, reading the documentation gave me both a whole lot of insight into what I was actually implementing and an idea of the correct way of doing things.
As developers, we often look for the quick implementation solution, but reading the documentation and really understanding the technology are invaluable when you come across tricky issues that don't seem to make sense.
I now knew that I had to set up a single Hazelcast cluster that all my microservices could read and write from. The Hazelcast cluster would live on our server where our microservices could communicate with it.
Setting up Development and Operations (DevOps)
In order to automate that process of getting hazelcast onto the server we needed 'DevOps': I had to set up a bunch of configuration files to describe and automate how Hazelcast would live and behave on our server and ultimately create a store that my microservices could write to.
I was rolling down the Yellow Brick Road meeting all sorts of technologies along the way. After a few hours of research, I had had the pleasure of encountering:
- Docker - This is a tool to build applications and deploy them. It allows you to bind your code and its dependencies into a Container (a package) which can be easily deployed.
- Kubernetes - The term itself comes from the Greek word for 'helmsman'. Kubernetes is like the captain of a ship that carries a bunch of passengers, who, in our case, are docker containers. Captain Kubernetes makes sure that the passengers are doing well and are all working as they should be.
- Helm - This is a package manager that makes it easier to get passengers on and off the boat by helping with deployments.
Daunting as these concepts seemed, my experience of the past few weeks was really starting to become valuable. Without realizing, I was slowly changing my mindset. I was taking time to understand the problems and picturing how I would solve them before even touching my keyboard.
I found a docker image for Hazelcast online, wrote a bunch of configuration files to describe how this container should be created and had a Hazelcast cluster on my local machine. I had mocked out, on my local machine, how Hazelcast would run on the cloud, which was essential before getting it to work on our live environment.
This achievement reminded me why I had signed up for this career: learning cool things and using them to solve problems. I even installed Linux to ease my DevOps journey, as a lot of the DevOps tools are much easier to configure and run on Linux than Windows, which made my team very happy. They'd been telling me to make the switch for a while.
When it came time to applying all that new knowledge and getting Hazelcast working on our Kubernetes cluster in the cloud, I looked towards the person in the team with the greatest knowledge in the DevOps space, Rameez: an intermediate engineer and all-round cool guy.
Tip 4: Find a mentor and do pair programming
It was time for pair programming: One of the best ways to absorb knowledge and grow together.
Essentially, it's exactly what it sounds like: you find a partner, one of you writes the code, the other acts as a kind of quality assurance. A second opinion on things. This helps to fill knowledge gaps and acts as a catalyst for higher quality code.
I took a seat next to Rameez and we started working on getting Hazelcast onto our Kubernetes cluster. Within a day, we had everything up and running in the test environment and set up our Hazelcast cluster to be easily deployable to our production environment after testing.
Hello, Emerald City -
I had finally slayed the Wicked Witch of the West, followed those seemingly endless yellow bricks and reached my proverbial Emerald City. I made a ton of friends along the way - Spring Caching, Hazelcast, Kubernetes, Docker, Helm and, of course had grown as a developer.
Your road as a grad will be a similar journey of growth and upskilling. It may seem like a lot at times, but you can rest easy knowing there are always other people to ask, documentation to consult and with time you will become comfortable with this crazy world of software development.
Resources
- A Guide To Caching in Spring - A quick start to caching with Spring-boot
- Rest with Spring Series - A series of tutorials that helped me get comfortable with a lot of Spring-boot concepts
- What is DevOps? - In Simple English - Easy to understand video explanation of DevOps
- The Illustrated Children's Guide to Kubernetes - A very high level video explanation of basic Kubernetes concepts
Some blog posts that helped me understand debugging better:
Fida is a junior software engineer with a passion for learning new technologies and writing clean code. Currently learning Vue.js, GoLang and GraphQL. You can find him under his Github or on Twitter