DEV Community

ThatGhost
ThatGhost

Posted on

Your /Models folder is lying to you

We've been trained to separate our POCOs into a neat folder. I'm here to tell you that folder is making your codebase harder to understand, not easier.

Open any .NET solution made by a developer who learned "clean architecture" from a blog post and you'll find it. The Models/ folder. Or maybe it's called DTOs/, or Entities/, or ViewModels/. Inside: 40, 50, sometimes 80 files. A graveyard of classes, half of which you have no idea whether anyone still uses.

This pattern is so common it's treated as a given. And I think it's quietly making our code worse.

The promise vs. the reality

The idea sounds reasonable: keep all your data classes in one place so they're easy to find. But what you actually get is a folder full of context-free classes with zero indication of where they belong or what they're for.

When you open UserSummaryDto.cs, do you know which service uses it? Which endpoint returns it? Whether it's still alive? You don't — you have to go hunting.

The folder doesn't organize things. It just physically separates data from behavior and calls it architecture.

Not every data object is the same

The real problem with the Models/ folder is that it treats all POCOs as if they are the same kind of thing. They are not. Before deciding where a model lives, it helps to think about what role it actually plays.

Some models are purely internal to a single service. They are implementation details, as private as any helper method. Nobody outside that service should ever know they exist.

Some models flow between classes or layers within the same feature. They are part of an internal contract, not quite private but still scoped to a specific area of the codebase.

Some models are genuinely shared across multiple services or features. They represent a concept that belongs to the whole application, and they deserve a clearly visible, shared location.

And some models are not really independent objects at all. They are logical subsections of a bigger model, like a name inside a user or an address inside an order. They only exist because their parent exists.

Throwing all four of these into the same Models/ folder collapses real structural differences into a flat list of files. The folder tells you nothing about scope, ownership, or lifetime. That information just disappears.

Treat internal models like private functions

Think about how you handle private functions. If a method is only used inside one class, you don't move it somewhere else "just in case." You keep it private, right where it belongs. Models should work the same way.

If a POCO is only ever used inside one service, it should be a private nested class inside that service. Not a separate file. Not in a shared folder. Private. Scoped. Invisible to the rest of the codebase, just like a private method would be.

public class UserService
{
    private async Task GetSummaryAsync(int id) { ... }

    // class and its properties only relevant to UserService, never to the outside
    private class UserSummary
    {
        public string DisplayName { get; set; }
        public DateTime LastActive { get; set; }
    }
}
Enter fullscreen mode Exit fullscreen mode

This keeps file count down and makes the intent obvious. Anyone reading that code immediately knows: this model belongs here and nowhere else.

When the model outgrows the service

Once a model needs to be accessible beyond the service itself, it earns its own file. At that point there are two approaches I use depending on how many models a service owns.

If the service has one or two models, a single companion file is enough. It sits right next to the service and the relationship is obvious from the name alone.

/Features
  /Users
    UserService.cs
    UserService.Models.cs   // owns all DTOs for this service
  /Orders
    OrderService.cs
    OrderService.Models.cs
Enter fullscreen mode Exit fullscreen mode

If the service grows and owns several distinct models, you can split them out into individual companion files, one per DTO, all still named after the service that owns them.

/Features
  /Users
    UserService.cs
    UserService.User.cs         // the core user DTO
    UserService.UserSettings.cs // settings owned by UserService
    UserService.UserAuth.cs     // auth data owned by UserService
Enter fullscreen mode Exit fullscreen mode

Either way, there is no Models/ folder at the root. Every file is named after the service that owns it, so you always know where a model comes from and who is responsible for it. The ownership is encoded in the file name itself.

Nested classes for nested data

The same principle applies when one class is conceptually part of another. Take a User that has a name made up of a first and last name. You could create a separate UserName.cs or NameUser.cs file, but that naming is already a hint that something is off. The class only exists because of User. It has no independent identity.

So just nest it:

public class User
{
    public Name FullName { get; set; }

    public class Name
    {
        public string First { get; set; }
        public string Last { get; set; }
    }
}
Enter fullscreen mode Exit fullscreen mode

Now the relationship is explicit in the code itself. You reference it as User.Name, which reads naturally and makes the ownership clear. You haven't added a file, you haven't invented a weird compound class name, and the data is as close to its parent as it can be.

What about shared models?

This is the fair counterargument. Some models genuinely are shared across services and they deserve a shared location. But that should be the exception, not the default. Most models in most codebases are not actually shared. They just live in Models/ because that's where models go.

If a POCO is used by one service, it belongs to that service. Putting it in a central folder doesn't make it more reusable. It just makes it harder to find, harder to delete, and harder to understand at a glance.

The test I use

When I create a new model I ask: is this used in more than one place today? If no, it becomes a private nested class. If it later needs to be shared, I'll promote it then. That's a much better signal than defaulting everything into a shared folder on day one.

Top comments (0)