Last time we discussed how to boost the performance of a food delivery service using advanced dispatching algorithms. Despite all the innovation on that front, dispatching algorithms don’t perform well when there are just not enough couriers around. To satisfy the expected level of demand, we need to hire and manage our couriers in a data-driven fashion, ensuring that there’s always the right number of couriers available all the time. In this article, we’ll discuss how a universal method from applied maths can help us to solve this problem.
After quite some time of working on dispatching at Careem, I’d like to show how dispatching evolves with the business and which classic mathematical problems are at the core of the service we all enjoyed during the lockdown. Imagine we develop a dispatching service for the new food delivery startup.
Caching is important to build efficient software. And there is a large variety of excellent caching libraries in Java that will help you with it. However, in Spring-based applications, all of them are primarily used indirectly via Spring caching abstractions. If you go through any Spring tutorial on caching you will be easily convinced that adding a cache to a Spring app is an absolute no-brainer.
Want to cache a result of the method invocation? Annotate this method with @Cacheable and Spring will do the rest. – Author X, Every Spring Tutorial
But when it comes to a slightly non-trivial caching problem or when the application is being heavily refactored, Spring force is leaving you and everything breaks apart. Let’s see why it happens and what are the alternatives.
Modern businesses demand higher flexibility of our applications. Emerging data-driven approaches to product development encourage A/B testing to improve product-market fit gradually. Continuous Delivery popularises decoupling of feature releases from the deployment cycle. All these techniques require some sort of dynamic configuration. Poorly implemented, the dynamic configuration makes the code fragile. Being not able to change the software frequently we lose all the benefits of delivering continuously and being data-driven. However, we can mitigate these issues if we stop mixing configuration code with business logic.
Spring Framework is widely known for its magic. It’s a holy grail for junior Java developers, pride for some senior and shame for the others. Spring combines old hacks that once made Java the privileged language of enterprise software development. However, most of these hacks are no longer needed and only lead to unmaintainable code. One of these hacks is the @Async annotation that makes methods asynchronous under the hood. Let’s discuss the problems with @Async and explore the modern alternative way to do asynchronous computations in Java.
Implementing anything inside a legacy codebase can be painful.
Legacy code. The phrase strikes disgust in the hearts of programmers. Although our first joy of programming may have been intense, the misery of dealing with legacy code is often sufficient to extinguish that flame. – Michael Feathers, Working Effectively with Legacy Code
That’s why we, developers, naturally tend to implement new functionality outside of it. With the rise of microservices, this tendency escalated to a whole new level. Anytime we want to implement something new, we think of creating a new web service. Applying this approach regularly, we add more moving parts. Losing control over this process, we risk creating a highly unreliable system.
We’ve already discussed how to sustain Reliable Services Communication for relatively new services. But what if we’re stuck with a legacy system that everyone is scared to change for some valid reasons?
Object-Relational Mapping (ORM) is one of the most controversial but still widely used tools in web application development. Ted Neward colorfully explained the most fundamental problems with ORM back in 2006, calling it The Vietnam of Computer Science.
In the case of automated Object-Relational Mapping, early successes yield a commitment to use ORM in places where success becomes more elusive, and over time, isn’t a success at all due to the overhead of time and energy required to support it through all possible use-cases. – Ted Neward, The Vietnam of Computer Science, 26 June 2006.
Despite all the blame, ORM appeared on all the projects I worked on so far. Unfortunately, most of them had unnecessary complexity due to the traditional use of the ORM. This time I’d like to discuss the roots of this problem and provide a solution to reduce the complexity and bring better modularity to your software system.
When we design a distributed system often comes a moment when we agree on the high-level architecture, but we’re still uncertain that it fits the business process we’re modeling. The most common way to overcome the dead end is to start building a full-blown solution and calibrate it along the way. Alternatively, we can use lightweight prototyping techniques. Representing distributed system design in a runnable code, we can enable experimentation and reduce risks associated with bad architectural decisions as early as possible. Let’s prototype a part of a large-scale delivery service using building blocks from RxJava.