Skip to main content

Command Palette

Search for a command to run...

Object-Oriented Development in Practice: Modeling System Responsibilities

Updated
4 min read
Object-Oriented Development in Practice: Modeling System Responsibilities

In my series on domain-driven object-oriented (OO) programming, I’ve used the Japanese vs. Dutch boat analogy to champion sustainable development speed—delivering valuable features with minimal waste, sustained over time. We’ve explored the science-engineering cycle, simplicity, inclusive modeling, small teams, and learning as the heartbeat of software. I’ve also shown how OO aligns with the Toyota Way’s lean principles, adapted for software’s fluid domain.

But what does OO actually look like in practice?

Too often, developers equate OO with persistent models: Person, Address, Order—data bags with getters, setters, and annotations. Frameworks like Spring Boot reinforce this, producing anemic entities and scattering logic across services, controllers, and configs. It “works,” but it’s procedural in disguise. The domain is hidden, and every new rule adds complexity instead of clarity.

True OO is different. It’s about modeling system responsibilities—what the software does, not just what it stores.

The Misconception: OO as Data Bags

Walk into most Java shops and you’ll see it: JPA entities wired to services. A Person class holds fields, while logic lives in @Service classes. It scaffolds quickly, ships fast, and tests pass. But rules get buried in conditionals, configs, and controllers. Need a new requirement? Patch a service. Switch storage? Rewrite everything.

This is the Dutch boat: full of managers and gadgets, but slow in the water.

OO done right builds the Japanese boat: lean, domain-first, efficient. Responsibilities live in objects, not scattered across layers.

Example: S3 Buckets Done Two Ways

Imagine an application that uses AWS S3. One bucket stores transient data for processing, another stores compliance logs permanently. Two approaches:

The OO Way: Explicit Responsibilities

Model the buckets as objects:

ProcessBucket – stores transient data with methods like store(Information info), read(String id), remove(String id). Its existence implies rules: TTL expiration, retries, cleanup.

PermanentBucket – stores immutable data with store(Information info) and read(String id), but no remove(). Its design enforces permanence, hinting at rules like versioning or audit trails.

Each bucket encapsulates its config, logic, and constraints. Callers don’t care about AWS—they just call processBucket.store(info). Add encryption? Change the object, not the app.

The Spring Template Way: Procedural in Disguise

In Spring Boot, S3 is wired via configuration:

A @Configuration class builds an AmazonS3Client from application.yml.

A StorageService routes calls: store(String bucketType, Object data, String key) with if (bucketType == "process") { … }. Rules like “no deletes on permanent” are enforced via conditionals.

A @RestController exposes endpoints, mixing domain logic with tech setup.

It works—files land in buckets—but rules emerge late, hidden in flags and services. Add versioning? More methods. Switch to Azure? Rewrite the service. It’s duct tape, not design.

Why Modeling Responsibilities Wins

Modeling responsibilities as objects unlocks ten advantages. Grouped into three themes, here’s why the OO way rows faster.

1. Design Clarity

Implicit rules surface early: A remove() method sparks discussion about TTL, retries, or forbidden deletes before coding.

Transparency: Responsibilities live in one place—no spelunking through services to find delete logic.

Readable code: permanentBucket.store(info) mirrors business language. The intent is obvious.

2. Team Efficiency

Discussability: Domain objects speak the business’s language. Compliance officers can review PermanentBucket without AWS jargon.

Testability: Objects test in isolation—no need to mock the whole cloud client.

Conceptual thinking: Developers ask “What should this bucket do?” instead of “How do I wire S3?”—a mindset shift toward design.

3. Future-Proofing

Adaptability: Add encryption to PermanentBucket without touching the app.

Reusability: The same bucket objects fit across projects.

Scalability: New responsibilities (e.g., ArchiveBucket) mean new classes, not bloated services.

Resilience: Objects outlive frameworks—no breakage when Spring changes config syntax.

Each advantage reflects lean principles: eliminate waste, empower people, and surface learning early.

Implications for Teams and Scrum

This approach reshapes team dynamics and process.

Small teams thrive: With explicit models, 3–4 people can manage systems of hundreds of entities. Complexity lives in the model, not in coordination overhead.

Scrum simplifies: User stories become goals (“Handle transient storage securely”) instead of tech tasks (“Implement S3 upload”). Planning centers on “what” the system should do, not “how” to wire services. Effort points are guides, not limits. Discovery drives the sprint.

The result: tighter vision, faster feedback, and fewer people rowing in circles.

Conclusion: The Japanese Boat in Action

OO isn’t nostalgia or boilerplate entities. It’s a way of making system responsibilities explicit—so rules surface early, designs stay clear, and teams move fast.

ProcessBucket and PermanentBucket aren’t storage utilities; they’re part of the boat itself, each with a clear role. The anemic approach reaches the finish line, but slowly, weighed down by scattered logic and hidden rules.

Modeling responsibilities makes the boat light, adaptive, and efficient—the Japanese boat. Try it: in your next project, pick one responsibility, model it as an object, and see what rules emerge.

You’ll find the code rows faster, the team rows in sync, and the boat keeps gliding—race after race.

More from this blog

E

Effective software engineering

69 posts

25 years modeling domains in enterprise & startups. Helping teams build systems that evolve, not explode. DM me if you want clarity before chaos. Model first. Code second.