DEV Community

Aditya Vardhan
Aditya Vardhan

Posted on

When Good OOP Goes Bad: Inheritance vs Composition in Production

What I Learned About API Design While Working on a Pydantic Logfire PR

I was working on a PR for Pydantic's Logfire project when I stumbled into something that completely changed how I think about API design. What started as a simple contribution led me down a rabbit hole that taught me how library maintainers actually make decisions in the real world.

How I Got Here

While digging through the Logfire codebase, I noticed some patterns that seemed connected to OpenTelemetry. Following that thread, I ended up looking at aiohttp's source code and found this interesting discussion about inheritance vs composition.

The conversation was about discouraging users from inheriting from web.Application - aiohttp's main class. At first, this seemed backward to me. Isn't inheritance a fundamental part of object-oriented programming?

The Problem

Users naturally want to extend aiohttp's Application class like this:

class MyApp(web.Application): def my_custom_method(self): return "something useful" 
Enter fullscreen mode Exit fullscreen mode

But the aiohttp maintainers strongly discourage this. Instead, they want you to use composition:

class MyApp: def __init__(self): self.app = web.Application() self.app['my_app'] = self def my_custom_method(self): return "something useful" 
Enter fullscreen mode Exit fullscreen mode

Why This Matters

Here's what I learned about the real reasons behind this design choice:

The Collision Problem: Imagine you create a method called foo() in your inherited class. Everything works great. Then, six months later, aiohttp releases a new version that adds a foo attribute to the base Application class. Your code breaks in weird, hard-to-debug ways.

Namespace Pollution: When you inherit, you're mixing your stuff with the framework's stuff. The maintainer put it perfectly: "it adds user names for base application, application attributes from future versions of aiohttp may clash with user ones."

Not Built for It: web.Application wasn't designed as a base class. The framework uses signals and callbacks instead of method overriding patterns.

The Trade-offs

The composition approach isn't perfect. You end up with circular references:

  • my_app.app points to the web application
  • app['my_app'] points back to your class

Type checkers also struggle with app['my_app'] - they don't know what type it is, while inheritance would give you proper typing.

One user in the discussion pointed out: "don't you find it to be a not-so-good OOP design?" They had a point.

What I Realized

This conversation showed me that API design isn't about textbook principles. It's about making hard choices between competing priorities:

  • Stability vs Convenience
  • Type Safety vs Flexibility
  • Clean Code vs Future-Proof Code

The aiohttp maintainers chose stability over convenience. They'd rather have users write slightly more verbose code than risk breaking existing applications when they add new features.

The Real World is Messy

Before this, I thought good API design meant following OOP principles and making things easy for users. But maintainers have to think about problems I never considered:

  • What happens when we add features in version 2.0?
  • How do we avoid breaking thousands of applications?
  • What if users depend on implementation details we want to change?

The maintainer even acknowledged: "I understand that the solution is not ideal. Maybe we need to come up with a better solution but it should be not an inheritance."

What This Taught Me

Working on that Logfire PR and following this thread taught me that good API design is about predicting the future and making conservative choices. Sometimes the "wrong" solution today prevents bigger problems tomorrow.

It also made me appreciate the thought that goes into the libraries we use every day. These decisions aren't made lightly - they come from experience dealing with real users and real problems.

Next time I'm frustrated with a library's design choices, I'll remember this conversation. There's probably a good reason for what seems like an arbitrary decision.


Have you encountered similar API design decisions that surprised you? I'd love to hear about them in the comments.

Top comments (0)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.