In this What is/How To, James Governor of RedMonk and Amir Rapson, CTO and founder of vFunction, delve into the challenges of managing technical debt, with a focus on determining whether an application’s architecture is good or bad.The video includes a short primer on the context and a demo of vFunction as a sniff test to quickly tell if your architecture is good or bad.
This was a RedMonk video, sponsored by vFunction.
Transcript
James Governor: Hey, everybody, it’s James. We’re here today thinking about technical debt. It’s one of those things that we don’t exactly have a good definition for it, but we know it when we see it. There’s general feeling that it’s always bad. How do we measure that? How do we get a better insight into what our technical debt actually is? How do we really understand what it is? How do we move forward? I think these are all good questions.
Today I’m here with Amir Rapson, CTO and Founder of vFunction. We’re basically going to talk about what is architectural observability, how to quickly tell if your architecture is good or bad. Who doesn’t want to know how to be able to do this quickly? Amir, welcome.
Amir Rapson: Thanks, James. Technical debt is a really important topic. One of the key factors of this technical debt is architectural technical debt, if your architecture is good or bad. The way you measure if architecture is good or bad is usually by just looking. If you want to rework a certain element in your architecture or rewrite it, how easy it is to rewrite it. In order to rewrite a certain element, you really need to think about all of its dependencies and all of its dependent elements.
If you have pre-existing dependent elements, you need to rework those as well. You want to minimize that in order to minimize the amount of work. When you think about that this way, it really means that architectural technical debt is minimized just by having less dependent elements. Or in other words, that modularity is the key to good architecture. So far-
James: Modularity Amir, let me ask you a question about that, because we’ve just spent seemingly a lot of time in this industry getting very, very excited and over rotating around the notion of microservices. Aren’t microservices modular? Are you saying that microservices is good architecture?
Amir: I’m not saying that microservices is not a good architecture, but I’m definitely saying that having microservices doesn’t mean that you have good architecture. Dependencies can mean a lot of things. Dependencies in a monolithic architecture can be calls between methods. Dependencies between distributed architecture, between microservices can be calls to other microservices. Still improving the architecture is removing dependencies, and these dependencies can be runtime dependencies, API calls between different microservices or interdomain calls.
You have good domain separation in your monolithic architecture. If you think about like a domain-driven design, then you can put each of these domains as a separate microservice, or you can put it in a logical domain or even a module inside your monolithic application. But still, these domain calls are runtime dependencies, method calls are runtime dependencies, and also database tables or resources that you use within your microservices can be runtime dependencies. Two different microservices, both the same database table, albeit not the best banner definitely out there, then you have a dependency.
You may have compile time dependencies. You have compile time dependencies from classes, libraries, binaries. If your microservices all use the same libraries and there’s a bug in one of those libraries, there is a lot to say about your libraries. I’m saying be conscious of them because they are your dependencies.
If you have a monolithic application, you definitely have a lot of classes in your dependencies for those applications. That’s definitely where microservices makes your life much better. But it’s important to say that when you think about moving from monolithic application to distributed one with microservices, don’t just throw away all these dependencies thinking if I do microservices, then everything’s going to be good, because essentially what you’re doing is stronghold or complexity that you have within your application to your DevOps team.
Another type of dependency that you need to be conscious of is really unneeded dependencies. You have so much of it. Dead code. Dead code could be code that you don’t use anymore. It could be code paths that you don’t use anymore. It could be an API from a microservice that you don’t call anymore. That’s half your microservice right there in your distributed application.
In your monolithic application, it could be dependencies that existed because we as developers don’t think about adding dependencies, we think about coding other methods. You actually added half of your dependencies there you’re never going to use. Those are all the things that take time. We found them to take an enormous amount of time. With monolithic applications, we found our clients to spend about 30% of the development time on code paths that never get executed. That’s an enormous amount of time.
James: That is properly technical debt.
Amir: This is a static analysis view of an application of a certain class. What we see here is a single class within a monolithic application and all of its compile time dependencies. They’re direct compile time dependencies, not [inaudible 00:05:43] dependencies. There is [inaudible 00:05:46] dependencies. It’s just one level of dependencies and all the edges are the dependencies between these classes. You see one big cluster of dependencies which is really impossible to handle. If you want to scale up this application or if you want to change this specific business logic class right here, you’re going to have to test everything that has to do with all of its dependencies. If you want to rework this [crosstalk 00:06:14].
James: Good luck maintaining and scaling this app.
Amir: Exactly. This is probably not the right field to tackle this. I think it is possible to tackle this application. This comes from an application that was or is green-handled, but definitely not just by using your static analysis view.
Another thing to avoid is really a complete mesh of services. Before when I mentioned that you could take microservice, for example here, this image in front of us, this is a screenshot from the D functional platform. Each of these bubbles is a service or domain in, here, a monolithic application, but it looks exactly the same with the distributed [inaudible 00:06:55] black hole in the middle.
I’m not going to go into these colors. These colors give you a little bit of the metrics the D function application tracks, but what you see here, all the dotted lines are calls between these domains. What you have here is a mesh of dependencies or a mesh of calls between these domains. Just by looking at the complexity here of this graph can tell you a few things.
First of all, there’s no found context in the application. Second, if you try to extract these different domains into services, and you have the microservice architecture, the application probably won’t work because of all of these dependencies and added latency and different ops. If you try to do that, you’re just going to make your ops people miserable having to keep this obligation up. This is not really a complete mesh. This is actually a complete mess, and it is definitely something to avoid.
James: We spend a lot of time these days talking about service mesh. Well, this is a service mess.
Amir: Other things to avoid, circular flaws between services and distributed application. Definitely see that. When a developer calls from service A to service B, he has no idea that service B calls service C and that service C calls service A. The reason he doesn’t know is that he doesn’t have the right tools to tell him that, like, “Oh, I know you called an API, but that API is not doing what we thought it was doing.”
Bi-directional dependencies. You see that in about 100% of the implications that we tackle for services in an application, just calling each other. If service A calls service B, most of the time, even in the same flow, service B will call back service A. This is impossible to scale up. Another thing to avoid.
But what is a good architecture? Let’s look a little bit about what’s a good architecture. This is just a picture I took from looking in my browser for images of a good architecture. What we see here is a layered architecture. We have consumers depending or calling API services, and these API services either call your core services or your integration services. We see this image, and we say, “Okay, this is the good architecture, but let’s think about why.” For instance, one thing that we see that these core services are not dependent on anything else, they’re dependent on themselves. There’s no dependency from core services onto other core services. There’s no bi-directional dependencies. They don’t call back to API services.
What it means is that each of them can scale independently, each of them can be deployed independently. It means that the number of dependencies that you have on them is limited by the number of layers that you have in your architecture. API services also communicate down, they don’t communicate to other API services. Integration services call only core services, and they don’t go up to API services. This is something that you can have by the having a very limited amount of architectural rules and layer of governance that your developers can make sure that the architecture stays sound.
Having microservices, this is just another pet peeve of mine. Having microservices is not an excuse not to have any order or any good architecture in your applications. The fact that you have microservices, you still need to maintain that architecture. The way to understand if you added these dependencies is really by using architectural observability. I’ll pause here and jump to a demo application. This is an anonymized application, from an online car rental company. The fact that you see the name Cars just because it’s a car rental company, not because it’s a company called Cars.
James: This was a real customer application?
Amir: This is a real customer application that I took away all of its identifying traits. Treat it as a typical application, but think about it also as a car rental application. You can think about what would be core services in a car rental application.
For instance, this Reservation Service right here. Reservation Service is you make a reservation. It’s a fundamental concept. It’s a fundamental service in this specific application. Like we saw in the image of what’s a good architecture, we saw that this shouldn’t have many dependencies on other services or definitely shouldn’t call any service. But what we see here in these dotted lines is that not only does it call other services, there’s a bi-directional dependency with other service calls here. For instance, you have this Notification Service.
Another core service that we see is right here, this Driver Service. Driver is of course another key concept or fundamental concept in a car info because in order to make a reservation, you probably have to have a driver. But the fact that you have a driver and you have a reservation doesn’t mean that driver needs to call reservation and reservation needs to call a driver. This is usually just bad coding due to the fact that developers didn’t have the right tools to understand that they’re creating this bi-directional dependency.
With an architectural-driven platform, you could just dive into individual paltry and see what happens when you getBookedOrOnceBookedReservation for a getDriverAccountResponse. You get the driver account, maybe provided with the list of dependencies through an integration service rather than calling from a core service with such a huge span of services to call the Reservation Service. Probably if I go up here through this API response, I might hit this Reservation Service again.
One last thing I want to show you is again, how often you see these jumbles of dependencies when we look at the static dependency. Let’s look at this ReservationServiceImpl class, service impl classes for business logic class. It’s not an entity, it’s not an interface, it’s really whole business logic. But when I look at its static dependencies, I will see something very similar to what I showed you before, a death star of dependencies. But I want to show you something more interesting even. vFunction as a platform doesn’t do only dynamic analysis or just static analysis. It does both dynamic and static. That’s why you actually saw all those calls and you can see calls to database tables.
But if we just focus in on this specific reservation service impl class, and we start to look at its dependencies, and we see all of these dependencies with the yellow dot. These yellow dots mean and be function, but these dependencies were not seen to be having any… They were not relevant, or they were not seen running in this specific context where the reservation service-
James: So these are all unnecessary dependencies?
Amir: These are all unnecessary dependencies. Try to focus your eyes on these yellow dots, and we start to see that there are more yellow dots than classes without yellow dots. Most of these dependencies related to this specific reservation class in every context are useless, are just there, are unneeded dependencies causing you lots of time on refactoring, affecting your scalability, affecting your resiliency [crosstalk 00:15:15]
James: And how big is that? How many lines of code is this?
Amir: This app is about 11,000 classes and about five million lines of code. Quite a sizable app, but having to now with the five million lines of code, split it into services and start to think about microservices seriously and distributed architecture seriously, definitely need to deal with all of these dependencies first in order for the application to perform well.
What I want you to remember from this demo and explanation about architectural observability is that technical debt is something to take care of. Architectural technical debt is an important aspect of technical debt. It’s important to provide the right tools to allow your developers and architects to understand if their architectures are good or bad, if they did something to reduce the quality of the application and the architecture, and to just be more aware of the long term effects that you have while coding and adding dependencies to your application.
James: Bad architecture is a lot of dependencies. Your idea is to bring architectural observability to reduce them. I think I got that, and I think that’s something that’ll probably resonate with a lot of… Anyone that has an estate of production applications, aka legacy apps, is going to recognize some of these issues. Amir, thanks very much for joining us. That’s another What Is/How To.