0

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?

asked Aug 21, 2023 at 15:47
8
  • how are those fields used at all if they are not even private on the domain model? Commented Aug 21, 2023 at 16:27
  • also, which version of clean arch are you following with domain and application layers? Commented Aug 21, 2023 at 16:30
  • 1. It's anemic model and that's why the properties are public. No logic there (at least currently). By assumption the User domain model is used to represent User which will flow over whole application. But it stores too few data and simultaneously enough for domain. It's because the domain is interested in user only, it doesn't need to know from which identity provider it comes while application layer at some points needs to know from where user comes. 2. I have never heard about clean architecture versions and when I google it I can see nothing appropriate Commented Aug 21, 2023 at 16:45
  • And one thing I didn't mention but maybe can be helpful. 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. Commented Aug 21, 2023 at 16:49
  • Domain, Application and Entity can mean different things. For example I don't understand how you can even set the externalId if the entity is internal to your datalayer but the domain object does not have it. Commented Aug 21, 2023 at 21:15

3 Answers 3

2

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.

answered Aug 22, 2023 at 1:11
3
  • 1. design of the domain model does not constrain what Application receives from Infrastructure so repositories interfaces at Application aren't forced to return a domain model? 2. Have you read comments below the question? I said there why (for me) it would be strange to have the extended version of User as domain model and I think it gives a better "look" for architecture. Commented Aug 22, 2023 at 5:46
  • 1
    @Szyszka947: (1) Correct. The domain models and the "repository return DTOs" (for lack of a better name) are separate types defined in separate layers. Note that I'm personally not a fan of this but several people who I consider to be strong devs advocate for this approach. (2) You mention "the domain is interested in user only", but the first approach I proposed inherently entails that the domain also facilitates the communication between other components (Application, Infra), which means that it has more of a vested interest, not just what the Domain personally cares about. Commented Aug 22, 2023 at 22:58
  • 1
    @Szyszka947: Maybe a better response to (2): your argument for not needing the extra fields in the domain model is rooted in the assertion that the domain models should only be modeled after the domain's logic (i.e. the fields it uses and cares about). This means that your domain does not take on any contractual responsibilities as to defining "the user" in a way that makes sense to your overall application. That's fine; but it's distinctly different from the first approach I offered, where the domain model is inherently used as the shared language between Application and Infra as well. Commented Aug 22, 2023 at 23:01
2

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.

answered Aug 22, 2023 at 17:47
1

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.

answered Aug 21, 2023 at 21:20

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.