Introduction
Open-source software (OSS) has revolutionized development, but recent licensing controversies with Fluent Assertions, MediatR, AutoMapper, MassTransit to name a few have exposed a painful truth:
"Free" isn’t free, and many popular libraries solve problems that don’t always need solving.
In my experience, overengineering is rampant in modern software. Teams reach for heavyweight OSS solutions when simpler, more maintainable alternatives exist.
The Overengineering Trap: When "Free" Libraries Add Cost
AutoMapper: The Unnecessary Abstraction
Problem it solves: "I don’t want to write mappings"
x.Name = y.Name
Reality:
- Most DTO mappings are trivial - a few lines of code.
- AutoMapper adds hidden complexity:
- Configuration quirks (ReverseMap(), ForMember())
- Performance overhead (reflection-based mapping)
- Debugging headaches ("Why isn’t this property mapping?!")
In my experience:
- 90% of AutoMapper use cases could be replaced with simple constructors or explicit mappings.
- The remaining 10% (deeply nested mappings) might justify it-but even then, manual mapping is often clearer.
MediatR: Do You Really Need CQRS?
Problem it solves: "I want a clean separation between commands and queries."
Reality:
- Many apps don’t need full CQRS.
- MediatR adds indirection:
- Hidden pipeline behaviours
- Magic string-based handler registration
- Licensing risk (now paid for v12+)
In my experience:
- Small to medium apps can just call services directly.
- If you need decoupling, a simple interface-based command pattern often suffices.
// Instead of MediatR:
await _mediator.Send(new CreateOrderCommand());
// Just do this:
await _orderService.CreateOrderAsync(request);
MassTransit vs. Raw Messaging
Problem it solves: "I need robust messaging with retries and sagas."
Reality:
- If you’re just sending basic messages, do you really need a framework?
- Raw RabbitMQ or Azure Service Bus may be simpler.
In my experience:
- Start simple. Only reach for MassTransit if you need advanced workflows.
The True Cost of "Free" Open Source
Licensing Bait-and-Switch
Recent examples:
- AutoMapper - Paid licensing for newer versions --> Lock-in or rewrite risk
- MediatR - Commercial licence for v12+ --> Suddenly a line-item cost
- Fluent Assertions - Switched to paid model --> Forced to fork or rewrite
- Redis - Moved away from pure OSS --> Forced cloud providers to fork
- Elasticsearch - Moved away from pure OSS --> Forced cloud providers to fork
If your core architecture depends on a library make sure you are aware its licence can change overnight.
Maintenance Burden
- Upgrades become risky (breaking changes, new licensing).
- Abandoned projects leave you stranded (e.g., Faker.js sabotage).
The "Just One More Dependency" Fallacy
Each new OSS lib seems harmless-until:
- You’re stuck on old versions (because upgrading is too risky).
- Your build is slow from 500+ transient dependencies.
- A security flaw in a deep dependency breaks production.
When to Avoid OSS (And What to Do Instead)
Manual Mapping > AutoMapper
Instead of:
CreateMap<Order, OrderDto>();
Do this:
public OrderDto MapToDto(Order order) => new()
{
Id = order.Id,
CustomerName = order.Customer.Name,
// Explicit is better than magic
};
Benefits:
- No licensing risk
- Easier to debug
- Better performance
Direct Service Calls > MediatR
Instead of:
await _mediator.Send(new CreateOrderCommand());
Do this:
await _orderService.CreateAsync(request);
Benefits:
- No indirection
- No surprise licensing costs
Raw Messaging > MassTransit (For Simple Cases)
Instead of:
bus.Publish(new OrderCreatedEvent());
Do this:
channel.BasicPublish("orders", "order.created", message);
Benefits:
- Fewer moving parts
- No framework lock-in
When Is OSS Worth It?
Good Candidates for OSS:
- Foundational tools - (e.g., PostgreSQL, .NET Runtime)
- Non-core utilities (e.g., Serilog, Newtonsoft.Json)
- Complex problems (e.g., ORM, distributed transactions)
Bad Candidates for OSS:
- Trivial problems (DTO mapping, basic messaging)
- Core business logic (where control is critical)
- High-risk licences (projects with a history of changes)
A Pragmatic Approach
The "Two-Pizza Rule" for Dependencies
If a library’s functionality can be rebuilt by a team smaller than two pizzas can feed, consider doing it in-house.
Wrap Critical Dependencies
If you really really really still require these then consider wrapping them, to make an exit plan viable.
// Instead of using AutoMapper directly:
public class OrderService
{
private readonly IMapper _mapper;
}
Wrap it:
public interface IDtoMapper
{
TDestination Map<TDestination>(object source);
}
public class AutoMapperWrapper : IDtoMapper { ... }
Benefit:
- Swap implementations without refactoring your whole application.
Regularly Audit Dependencies
- Use dotnet list package to track transitive dependencies.
- Check licences with FOSSA, Snyk, WhiteSource or Veracode.
Conclusion: Simplify Before You Depend
The recent licensing controversies are a wake-up call: Every OSS dependency is a potential liability.
In my experience:
- Many projects overuse frameworks for problems that don’t need them.
- Manual solutions are often simpler, faster, and safer.
- Licensing risks are real-plan for them.
Ask Yourself:
- Are we using AutoMapper/MediatR/MassTransit because we need it-or because it’s the default?
- Could we replace this with 50 lines of manual code?
- What’s our plan if the licence changes tomorrow?
Remember: The best dependency is the one you don’t need.