Blog
Mert Özen's blog posts on software development, backend, frontend, and DevOps.
Component Principles: Splitting a .NET Solution into Projects (REP, CCP, CRP, ADP, SDP, SAP)
When splitting a solution into projects, classes that change together belong in the same project, dependencies must be acyclic and flow toward stability, and stable projects should be abstract. Measuring the cost of a Common project everyone depends on, computing instability and abstractness on a real solution, and reading the main sequence correctly.
Lesser-Known Principles: Separation of Concerns, Least Astonishment, Fail Fast, CQS and GRASP
The principles outside SOLID drive most everyday decisions: Fail Fast catches errors at startup, CQS keeps a question from changing the answer, and GRASP asks which class a responsibility belongs to. Catching a configuration typo with options validation, the Math.Round surprise, side effects in getters, Explicit Dependencies, Persistence Ignorance and deciding when principles collide.
Composition over Inheritance: Fragile Base Class, Delegation and Pluggable Behavior
Inheritance ties a subclass to its base class's implementation details, so a harmless change in the base class can silently break a subclass, while composition and delegation depend only on the contract. The fragile base class problem shown with real output, the class explosion when behavior grows along two axes, and where inheritance is right: Collection<T> hooks and sealed.
DRY, KISS and YAGNI: Duplication, Simplicity and "You Aren't Gonna Need It"
DRY means a single source for knowledge, not for code; merging two rules that change for different reasons just because they look alike is not DRY but a wrong abstraction. Backing out of a wrong abstraction as Metz suggests, the rule of three, measuring KISS with CA1502, and the four costs Fowler lists for YAGNI.
Dependency Inversion Principle: DIP vs DI vs IoC and the Composition Root
DIP asks for the compile-time dependency to flow from details to abstractions and for the abstraction to be owned by its user; DI is only the technique of supplying dependencies from outside, and IoC is control of the flow moving to a framework. Which project the interface belongs in, using Program.cs as the composition root, and why a service locator defeats DIP.
Interface Segregation Principle: Fat Interfaces and Role Interfaces
ISP means no client should be forced to depend on methods it doesn't use, and interfaces are cut by client (role interfaces), not by class. One method added to a 14-method IOrderRepository breaking three files, splitting into read/write/report roles and forwarding in DI, IReadOnlyList<T> and IList<T> still separate in .NET 10, single-method interface inflation and the cost of piercing ISP with default interface methods.
Liskov Substitution Principle: Behavioral Subtyping and NotSupportedException in .NET
LSP requires a subtype to satisfy not just the compiler but the base type's contract: preconditions aren't strengthened, postconditions aren't weakened, invariants and the history constraint are preserved. Three real violations in Bookstore's stock module, the NotSupportedException that T[] and ReadOnlyCollection throw from IList<T>.Add, capability queries with Stream.CanSeek, and verifying the contract against every implementation with an abstract xUnit test class.
Open/Closed Principle: Extending Without Modifying and the Speculative Generality Trap
OCP means adding new behavior as a new implementation without opening existing, working code; but any code can only be closed against a particular axis of change. Closing a growing notification switch with INotificationChannel and .NET 8 keyed services, HttpClient's DelegatingHandler chain, lightweight extension points with Func, and the cost of speculative generality.
Single Responsibility Principle: Not "One Thing" but One Actor
SRP does not mean "a class does one thing"; in Martin's definition a module should be responsible to one actor, the single person or group that requests the same change. An Employee example where an accounting change breaks the HR report, sharing data while separating behavior, a Facade, .NET's Stream/StreamWriter/Encoding split and the fragmentation trap.
Coupling and Cohesion: Two Measures of Good Design, Connascence and the Law of Demeter
Good design means weak links between parts (coupling) and strong unity inside each part (cohesion); connascence grades those links by kind, and the Law of Demeter limits how far objects reach into each other. Kinds of coupling and levels of cohesion, connascence of position in a six-parameter constructor, the TimeSpan(10) trap, and protecting a wallet balance with Tell, Don't Ask.
Why Design Principles Exist: Change, Dependencies and a Short History of SOLID
Design principles exist to name the unplanned dependencies that make change expensive and to draw boundaries around them: rigidity, fragility, immobility and viscosity share dependency as their root. The history from Parnas's information hiding to Feathers's SOLID acronym, and the series' Bookstore order and notification example.
Bulk Import with a Validation Report: Upload Excel/CSV, Staging Table, Row-Level Error File
The safe way to bring in a large, dirty Excel/CSV file is to load it into a text-column staging table with SqlBulkCopy, apply the rules to every row with set-based SQL, give the user a row-level error file and make the commit a separate, confirmed step. This post walks through a 20,000-row import with real output, including 202 Accepted plus a status endpoint, idempotency via a file hash and escaping against CSV injection.