The Frameworks Dilemma | HackerNoon

As a developer, frameworks are usually the first thing you grab when you want to speed things up and keep things reliable. People often talk about frameworks like they’re the perfect solution that can fix all your problems, making development faster, easier, and more efficient. However, if you’ve had some experience under your belt, you know that frameworks aren’t a one-size-fits-all solution. Picking the right one can streamline your work, but the wrong choice can lead to headaches down the road, slowing you down just when you need to move quickly.

In this blog post, we’re going to dive into the real challenges and strategies that come with choosing and using frameworks. We’ll look at the potential pitfalls, how to avoid them, and ways to keep your codebase flexible — even when a framework is in play.

The Relationship You Didn’t Know You Were Getting Into

Committing to a framework is a bit like getting into a long-term relationship. And it’s not something to take lightly. Unlike a simple library or a small utility, frameworks come with opinions — lots of them. They impose structure and methodology on your application, whether you like it or not.

The Long-Term Commitment of Frameworks

It’s crucial to remember that framework creators have their own priorities. They’re solving THEIR problems, not yours. They don’t owe you anything (unless, of course, you’ve got a buddy on the internal framework team, in which case, lucky you). If things go south, especially deep into your project, you could be in for a world of hurt. Now you’re stuck fixing it, or worse, ripping it out entirely.

Not fun, right?

So before you tie yourself to a framework, make sure it actually fits what you need. Otherwise, you’re rolling the dice.

FAANG Problems

Author has no idea if FAANG became MAANG, MANGA, or if we’re all just in an anime now.

Here’s where experience really counts. When companies grow rapidly, they often face challenges that no off-the-shelf solution can handle. The scale of these problems forces them to create their own tools — custom databases, ETL engines, BI tools — you name it. Big Tech giants like Google, LinkedIn, Spotify, and Netflix have led the way, building and open-sourcing tools that the rest of us now get to play with.

But here’s the thing: these tools weren’t built to be universally applicable. They were created to solve specific problems that most companies will never encounter. Engineers who’ve worked at these big companies are used to dealing with these kinds of challenges — they’ve built solutions that operate at a scale most of us can only imagine. So when they move to smaller companies, the framework and tooling decisions they make are based on a deep understanding of both the power and the pitfalls of these technologies.

Pushback Against Frameworks

Lately, there’s been a bit of a rebellion brewing—people are getting tired of frameworks. Especially in the JavaScript world, developers are fed up with the constant churn. You’ve probably seen it: every time a major update drops, you have to rewrite significant chunks of your codebase just to stay relevant. And don’t get me started on the endless cycle of breaking changes.

This frustration has given rise to a revival of simpler, more stable stacks. Stuff like vanilla HTML, CSS, jQuery, PHP, and SQLite are making a comeback among developers who prioritize getting things done over staying on the bleeding edge of tech. Yeah, it might feel a bit “old-school,” but it’s far from obsolete. With a simpler stack, you can iterate fast and ship even faster. Sure, newer frameworks like React, Node.js, and Flask have their place, but sometimes you don’t need all the fancy stuff. Sometimes, sticking to what works can save you a whole lot of headaches.

The Framework Industrial Complex?

Are frameworks becoming… overhyped? It’s hard not to notice that some frameworks feel more like tools designed to lure in VC funding than to solve real developer problems. It’s like there’s a whole ecosystem pushing developers into these frameworks, only for them to realize later that, once they scale, they’re locked into expensive platforms. Sure, frameworks like Databricks are open-source and free to start with, but as you grow, you’re nudged towards their enterprise solutions. And suddenly, your hosting and operational costs are through the roof, while a simple VPS might have sufficed.

It feels a bit like a trap, doesn’t it?

Delaying the Framework Decision

Here’s a piece of advice that I swear by: don’t rush into choosing a framework. Don’t commit until your architecture is fully fleshed out.

The framework should be the last thing you worry about, not the first.

First, make sure your architecture is solid. Know your core components and how they’ll interact. Once you’ve got that, you can evaluate frameworks with a clear understanding of where they might fit — or if they even fit at all.

This approach ensures that your design is solid and suited to your specific needs. When it comes time to consider a framework, you’ll be able to see clearly where it can enhance your architecture without limiting it.

Before you jump into using any framework, ask yourself: do you really, truly need it? Sure, frameworks can add layers of automation and convenience, but they also come with their own set of limitations. If your application has unique requirements, frameworks might not play nice with them.

Think long and hard about the long-term benefits against the limitations.

Making Frameworks Expendable

If you decide a framework is worth the risk, make sure it’s easy to replace. Yes, you heard that right. Build in some flexibility so that if you need to ditch it later, it’s not a monumental task. Here’s how:

Abstract Your Dependencies

Keep the framework’s grubby little hands out of your core code. Use interfaces to abstract the framework’s functionality so that your business logic doesn’t depend on the framework directly.

Let’s say you’re using TensorFlow for machine learning. Instead of embedding TensorFlow code throughout your entire application, define interfaces to keep things neat and abstract:

from abc import ABC, abstractmethod
import tensorflow as tf

class ModelTrainer(ABC):
    @abstractmethod
    def train(self, data):
        pass

class TensorFlowTrainer(ModelTrainer):
    def train(self, data):
        # TensorFlow-specific training logic
        model = tf.keras.models.Sequential([...])
        model.compile(optimizer='adam', loss='sparse_categorical_crossentropy')
        model.fit(data, epochs=5)
        return model

By doing this, your core logic isn’t tightly coupled with TensorFlow. If you need to switch to another machine learning framework, it’s just a matter of swapping out the implementation.

Dependency Injection (DI) is Your Friend

Next, let’s talk about Dependency Injection (DI). This technique lets you inject specific implementations of your interfaces into your classes, keeping your codebase decoupled and modular.

class TrainingPipeline:
    def __init__(self, trainer: ModelTrainer):
        self.trainer = trainer

    def execute(self, data):
        return self.trainer.train(data)

# Inject the TensorFlowTrainer implementation
pipeline = TrainingPipeline(TensorFlowTrainer())

Now your code is flexible, easy to test, and ready for whatever the future throws at it.

Embrace Inversion of Control (IoC)

For the ultimate in flexibility, take things up a notch with Inversion of Control (IoC). This pattern allows you to specify implementations in a configuration file or a centralized location in your code. It’s the cherry on top of your framework-agnostic architecture.

Here’s an example of how that might work with a configuration-based approach:

# config.py
class Config:
    TRAINER = 'my_project.trainers.TensorFlowTrainer'

# main.py
import importlib

class TrainingPipeline:
    def __init__(self, trainer_class: str):
        module_name, class_name = trainer_class.rsplit('.', 1)
        module = importlib.import_module(module_name)
        trainer_cls = getattr(module, class_name)
        self.trainer = trainer_cls()

    def execute(self, data):
        return self.trainer.train(data)

# Inject the trainer specified in the configuration
from config import Config
pipeline = TrainingPipeline(Config.TRAINER)

Now, if you ever need to replace TensorFlow with another machine learning framework, you simply update the configuration and carry on. No hassle, no drama.

Conclusion

Remember, frameworks should serve YOUR architecture, not dictate it. With careful planning and strategic abstraction, you can reap the benefits of frameworks without getting trapped in long-term dependencies. The trick is staying in control. So next time you’re about to dive into a framework, take a step back and remind yourself: you call the shots here.

Ready to be hurt


Thank you for reading!

Any questions? Leave your comment below to start fantastic discussions!

Check out my blog or come to say hi 👋 on Twitter or subscribe to my telegram channel. Plan your best!