Can the application layer extend the domain models?

  softwareengineering

I write BFF (Backend For Frontend) and it’s domain is to provide user info and microservices integration.

The User domain model looks like below:

public class User
{
    public required Guid Id { get; set; }
    public required string Name { get; set; }
    public required AvatarUrl AvatarUrl { get; set; }
}

The domain have to know user information. But the application layer needs to know also from which identity provider user comes. To store that information, my User entity in Infrastructure layer looks like it:

internal class User
{
    public Guid Id { get; set; }
    public string ExternalId { get; set; }
    public ExternalIdentityProvider ExternalIdentityProvider { get; set; }
    public bool UseExternalProviderProfile { get; set; }
    public string Name { get; set; }
    public string AvatarUrl { get; set; }
}

So I wanted to write some handler in Application layer to retrieve user information, and here problem occurs because the Domain model has not enough information. The domain model doesnt tell me whether shall I call identity provider or just use information stored locally.

So I need to “extend” the Domain model in Application layer, so I can get necessary information about User. But here next problem occurs because my Repository won’t return the User Domain model. It will return the extended user model.

The current User domain model is enough for most logic, the case where it isn’t is some sort of of “special case”, but this special case is an entry point to rest of logic.

What to do in this case? The simplest way would be to just extend the existing Domain model instead of creating extended model in Application layer. But the Domain doesn’t need to know all these things which are needed by Application layer. What to do?

8

Inherently, how you’ve designed your architecture and what you intend to do with it are at odds with one another. The thing you’re trying to achieve is intentionally prevented by your current architecture.

Based on your domain and infrastructure models, the clear inference here is that ExternalId, ExternalIdentityProvider and UseExternalProviderProfile are private implementation details of the Infrastructure layer. Based on that, it doesn’t make sense that you want to now have the application layer aware of these private implementation details.

Something’s got to give, but I can’t decide for you which thing is the one that needs to give.

I also want to take the time to point out here that you’ve been very vague about what the Domain represents here, there are several subtly different ways how the Domain behaves in relation to the Application/Infrastructure layers and these differences impact what the better answer to your question is.

Based on how I work with a domain layer, which is as a kind of “shared language” between the Application and Infrastructure layers; anything that needs to be shared between these layers needs to inherently be part of the shared language, ergo the domain. Essentially, the only parts that aren’t in the shared language are the private implementation details of each layer, since these are by definition private and thus not shared with anyone else (I’m ignoring the possible exception of test projects).

Going by the above approach, if you need this information to be communicated outside of Infrastructure, it must be part of your domain. That doesn’t necessarily mean that it needs to be part of the User domain model, but there has to be some kind of domain model that is able to represent this information.

Based on how I’ve seen others work with their domains, there is no link between Domain and Infrastructure. The Application layer fetches data from Infrastructure, and it then chooses what to do with it – return it to the consumer, or input it into a domain object/model if needed.

Going by this second approach, your question would be rendered moot, as the Application inherently accesses Infrastructure without any Domain involvement, so the design of the domain model does not constrain what Application receives from Infrastructure.

I am not familiar enough with other possible ways to architect a domain layer – but I’m sure there’s more ways than I’ve listed. If you can elaborate on your architecture, I could add it to the answer.

3

The unclear part about your question is why you need this extra information in the app, but don’t consider it part of the domain.

Senario 1.

The information is only needed by the infrastructure layer. For example, you can log in using one of several auth services, facebook, google, or an api key. But once logged in that information has no bearing on the application. So You have:

UserRepo
public User GetUser(string id, string selectedLogonMethod)
{
   if(selectedLogonMethod == facebook) ...

}

here you can pass in the selected method each time, it doesn’t need storing and is never used again and you have no problems. Maybe you need to use it again for a refresh token, but it can just be held in memory for that purpose later.

Senario 2.

The Information is used by infrastructure AND needed to make the app work. for example, you choose facebook, google api key etc, but API key users get more requests per second than facebook users

Here you can just call that domain logic and add it to the domain object. Or create some related field “IsSuperUser” which does the same thing. No problems

Senario 3.

The information is definitely NOT needed to make the app work, BUT the infrastructure layer needs to be passed it in ways where simple in memory storage of how the user logged in isn’t going to work?

Here you can try a number of clever things, store separately, use a different user object, or fall back to one of the two other methods, but we have nothing to go off to recommend one over the other. Or to know why a solution might conflict with your chosen architecture.

If you don’t want to extend the User class in the Domain model, you could introduce a separate class

internal class UserIdProvider
{
    public Guid UserId { get; set; }
    public ExternalIdentityProvider ExternalIdentityProvider { get; set; }
    public bool UseExternalProviderProfile { get; set; }
}

in your infrastructure or application layer, and introduce a dictionary Dictionary<Guid,UserIdProvider> there. This will allow you to create a UserIdProvider object for each User object, and associate the two by the UserId.

LEAVE A COMMENT