GRASP: the design principles you should know before SOLID

GRASP - General Responsibility Assignment Software Patterns - The father of SOLID When we talk about GRASP, we are talking about the masterpiece Craig Larman wrote in 1997. SOLID is the natural result of applying GRASP. But what is GRASP? A set of principles that is earlier, more complete and more powerful than SOLID. GRASP stands for General Responsibility Assignment Software Patterns.

Craig Larman didn't invent anything; he gathered and formalized responsibility assignment patterns that good software designers already applied intuitively. A few years later, Robert C. Martin compiled five object-oriented design principles in his paper "Design Principles and Design Patterns" (2000), and Michael Feathers coined the acronym SOLID. Although GRASP and SOLID were born independently, GRASP grounds and explains why SOLID works when it works.

SOLID has become dogma, and dogmas are very dangerous in engineering. SOLID tells you what to do, but it doesn't teach you how to think. It is essential to know that SOLID is the answer generated by a set of principles called GRASP.

GRASP is the one that gives you the basis to understand why SOLID works when it works.

A developer who only knows SOLID tends to create interfaces for everything "because you have to depend on abstractions", to obsessively separate classes "for the single responsibility" without being clear about what criterion they use to define that responsibility, and to apply patterns mechanically without asking whether the context justifies it.

GRASP gives you the criterion that SOLID doesn't. It teaches you to ask the right questions before making design decisions.

You can download this masterpiece in PDF below.

Which frameworks apply the GRASP principles?

Spring (Java) and Symfony (PHP) are the ones that most faithfully implement the nine principles. ASP.NET Core applies most of them natively: DI, middleware, stable interfaces, high cohesion in its modules. Django applies several with good results, although its Active Record ORM detracts from Low Coupling purity. NestJS could apply them all; it's the JS framework that comes closest.

Spring (Java) — Implements all 9 principles. IoC container, factories, stable interfaces, cohesive modules
Symfony (PHP) — Implements all 9 principles. Directly inspired by Spring, DI container, EventDispatcher, decoupled components
ASP.NET Core (C#) — Applies most of them natively: DI, middleware pipeline, interfaces throughout the hosting system
NestJS (Node/TS) — The closest in JavaScript. Modules, DI, guards, interceptors, pipes
Django (Python) — Applies several principles, but its Active Record ORM compromises Low Coupling
Laravel / Rails / Express — Prioritize delivery speed over design principles

The pattern that repeats is clear: frameworks born in the enterprise world (Spring, Symfony, ASP.NET Core, NestJS) tend to apply GRASP because they face large, modular projects where robustness, security and scalability must be core.

Those born in the startup/prototyping world (Rails, Laravel, Express) prioritize delivery speed and sacrifice design principles in exchange for immediate productivity.

None put "GRASP" on their landing page, but when you read their source code, you can tell who has done their homework and who hasn't.

Let's get to the point: the 9 GRASP principles

1. Information Expert

Who has the necessary data to do this?

Assign each responsibility to the class that already has the information to fulfill it. It seems like common sense, and it is, but it's constantly violated.

A day-to-day example: you have an Order class with its order lines, quantities and prices. Who should calculate the total? The Order itself, because it has all the data. Not an OrderService, not a PriceCalculator, not a loose helper.

When you see business logic scattered across services that access an object's properties to make decisions about it, you're looking at a violation of Information Expert. That logic should live in the object that owns the data.

2. Creator

Who should create this object?

An object A should create an object B when A contains B, A uses B directly, or A has the necessary data to initialize B.

Don't confuse with Symfony's DI container

Creator answers which class has the logical responsibility to instantiate a domain object. The DI container is a technical mechanism that resolves dependencies between services. They are different layers of the same design.

If your system generates invoices from orders, who creates the invoice? Whoever has all the order and line information. Not a controller, not an API endpoint. The creation responsibility goes where the data and context are.

This doesn't mean you can't use factories. It means the factory should exist when there is a concrete reason for it (complex creation logic, multiple variants), not by default.

3. Controller

Who coordinates this use case?

Careful: this isn't the MVC controller. It's the object that receives a system request and coordinates the workflow, delegating each step to the appropriate class.

Think of an orchestra conductor. They don't play any instrument, but they know when each section comes in. A GRASP controller receives the "update user" request, checks permissions (delegating to the expert), validates data (delegating to the validator), persists changes (delegating to the repository) and records the action (delegating to the auditor). It contains no business logic of its own.

4. Low Coupling

How many dependencies does this class have? Are they all necessary?

The fewer direct connections between classes, the easier it is to modify, test and reuse each one. This isn't new, but GRASP presents it as a constant evaluation criterion, not as an abstract goal.

An invoicing module that depends directly on the email library, the concrete database connection and the cache system has high coupling. If it depends on interfaces that abstract those three things, the coupling drops dramatically.

Now this is Symfony's DI container

Before Symfony's DI container, if A depended on B and B in turn depended on C, there was strong coupling because if A or B changed, the problem would cascade. For example: new A() depends on new B(), which in turn depends on new C(). Each new is a direct coupling point.

The DI container solves this: you declare what interfaces each class needs, and the container assembles the entire dependency tree automatically, without any class knowing the concrete implementations it depends on.

5. High Cohesion

Is everything this class does related to each other?

High cohesion means a class has a reduced set of closely linked responsibilities. It's the direct complement of low coupling: if you decouple without criteria, you end up with classes that do too many unrelated things.

The classic anti-pattern is the AdminService or Utils class that accumulates unrelated methods: validate users, send emails, generate reports and export CSV. Each of those responsibilities should be in its own cohesive class.

6. Polymorphism

Am I using conditionals to decide behaviors that vary by type?

When you have a switch or a chain of if/else that chooses behavior based on a type or category, that should be solved with polymorphism: a common interface and multiple implementations.

For example, a notification system with email, SMS and push sending. Instead of a method with three conditional blocks, you define a Notifier interface and three classes that implement it. Adding a fourth channel (Telegram, Slack, whatever) requires no touching existing code.

7. Pure Fabrication

Does no class in my domain fit this responsibility?

Sometimes you need a class that doesn't represent anything from the real world or the problem domain. It's not a user, nor an order, nor a product. It's a technical artifact that exists because your architecture needs it.

Repositories, loggers, session managers, serializers, dispatchers. None of them model a real entity. They are pure fabrications, and GRASP explicitly recognizes them as legitimate.

Important

Never try to force these responsibilities into your domain entities.

8. Indirection

Do I need an intermediary so these two components don't know each other directly?

GRASP is better than SOLID - Software design principles

Insert an intermediate layer between two components to avoid direct coupling. Middleware, message buses, event dispatchers, adapters. All are forms of indirection.

Your HTTP controller shouldn't talk directly to the database. Your use case shouldn't know if the notification is sent by email or via Slack. An intermediary absorbs that dependency and decouples both parts.

Each layer of indirection adds complexity to the flow; you shouldn't overuse it. Use indirection when the coupling you remove justifies the complexity you add.

For example: when we design an invoicing system. Sales doesn't know and doesn't want to know how an invoice is generated. It just says "hey, invoice this" and moves on. The invoicing service receives the task, calculates taxes, numbers, saves and sends whatever needs to be sent. If tomorrow the tax rules change or the database is migrated, sales keeps doing the same as always. Each to their own.

9. Protected Variations

Which parts of my system are going to change? Are they shielded behind a stable interface?

Identify points of instability or variation in your design and protect them with interfaces. If you know the payment gateway might change, the storage provider might migrate, or the export format could be extended, those points need a stable interface in front of them.

This doesn't mean putting interfaces on everything. It means analyzing your system, identifying what is likely to change, and protecting those specific points. What isn't going to change doesn't need additional abstraction.

This isn't modularity

You can have a perfectly modular system that still cascades-breaks when the payment gateway changes, because even though it was in its own module, twenty classes depended directly on the concrete Stripe implementation without an interface in front.

Protected Variations tells you "out of everything you have, figure out what's going to change and protect that". One is structure, the other is criterion.

Conclusion

SOLID gives you five rules. GRASP gives you a thinking model. A developer who understands GRASP applies SOLID naturally, because they understand the foundations underneath. It doesn't work the other way around: you can memorize SOLID and still make bad design decisions because you lack the reasoning framework.

The difference is the same as between following a recipe and knowing how to cook.