It has been an industry mantra for years now: monoliths are unwieldy, inflexible beasts that halt any and all innovation in its tracks. But many companies have monoliths in one form or another that they've been trying to break down - such as traditional ERP and MRP systems or large-scale transaction systems.
Believe it or not, there are some good things to be said about monoliths. Ellen Körbes, manager of developer relations at Garden, for one, pointed out in a recent presentation that "there is something that we had with monoliths because of this simplicity the magic of programming was there." She recalls how with monoliths, it's easy to learn the system for the first time. You have one large system, so you have one programming language, you have one set of rules, one set of conventions, and you have one big one-build command."
Of course, Körbes doesn't see monolithic systems in anyone's future, and offers an open-source platform to break down and rebuild a microservices architecture.
In a separate presentation, Kelly Sutton, engineering manager at Gusto, discussed the implications of monolith-smashing -- particularly those built on Ruby on Rails -- but his advice can be applied to many programming languages and frameworks that have grown too big to fail.
For starters, the problem with moving functionality out of a monolith and into microservices is that both versions will need to be maintained and updated, Sutton points out. "We've actually created a problem that is worse than we had," he explains. "We literally created tribal knowledge in our system. Now whenever we want to go find an HR concept we have to go ask someone, 'hey, is that an HR v2 or is that in the old HR appendage that is just kind of sticking around?'"
The problems grow as applications or services scale. As the decision is made to break out a function as a service, such as HR names, addresses and personal identification numbers, data and functions may still be shared between the new and old applications -- HR v1 and HR v2.
Sutton illustrates what issues arise as monoliths are broken apart:
"We enumerate all of the things that HR v1 does today. Let's say it's like 14 things, and then we slowly start connecting it back to the main application, meticulously going one by one and moving those 14 behaviors over. But usually about like halfway through it becomes someone's full-time job just to say, 'okay what's in HR v2 and what's in HR v1.' Go a little bit further and you're still getting bug reports against the old HR system, so now you're saying, 'okay, so I guess we fixed the bug in the old HR system, but we need to recreate it in the new one and then fix it there.'"
The following are recommendations for breaking apart monoliths without complicating matters further:
Avoid circular dependencies. How does one break apart a monolithic system and develop a microservices-based architecture? "The answer is 'painfully,'" says Körbes, who notes monolithic dependencies are being swapped from another form of dependencies. "Service dependencies are a pain to manage. If I change my shipping service, I might have to update my orders service, and if I change my orders service, I might have to update my front-end service, because things cascade. I can't just change one thing and expect it not to be there." The difference with monolithic applications is "you have a compiler, and the compiler could do the dependency graph for you.".
Use value objects to address scaling issues. As an example, Sutton says, "we have a service class or object that handles 'what should happen when a company signs up in our application?' We're gonna send an email and increment some stats trainer counter. This looks innocuous enough. Small applications can do this. But what we've actually done is forever coupled our company mailer in our stats tracker to the structure of our company model. This is probably not that big of a deal, but as our application grows, it can be very, very difficult to untangle. So instead, we peel just the values that we need off of those rich active record objects, turn them into values. maybe give those bundles of values, and then just pass those along to whatever subsequent class or method that they need to do their job right."
Move slowly. Breaking apart monoliths into distributed services is going to require some careful deliberations. "You have a big pile of code and you split it into 20 different chunks and you put some padding into it and some of these chunks are now a different programming language," says Körbes. When you go to microservices, you're adding stuff to it, so it's going to be slower." Plus, there is perhaps hundreds of additional programming hours involved, Sutton says. "You have to sit down with your product teams and say, 'Okay, we're gonna do this and then things are going to get faster over time but for now nothing is going to change. We're going to refactor right. You need to make a strong business case for how this is going to help you speed up and help you develop safer software, no matter how bad the current code is today."