C
C#9mo ago
sepp

✅ .NET Core N-Tier API Project question about repository pattern.

Hi, I am creating a web api project using n-tier architecture, I have API, Entities, DAL(Data access) and Services layer. I have implemented generic repository in DAL layer and now I am creating services in Service layer. I was thinking of a architecture which API layer communicates with Services layer and Services layer With DAL which also has a project reference to Entities layer. But in this way I have to do it like in the picture "new repository()" I added but some say it is bad to use it like this. What is best approach?
94 Replies
sepp
sepp9mo ago
Please ignore constructor parameter, its without a parameter actually
Pobiega
Pobiega9mo ago
You are ignoring the constructor parameter userRepository which seems... odd okay just saw your comment, well why is it without a parameter? you definately should be injecting a repository into your service, if you're going to have repositories
sepp
sepp9mo ago
I am a newbie and very lost, I searched for tutorials using n-tier architecture and repository patttern but cannot find any resource
Pobiega
Pobiega9mo ago
how on earth
sepp
sepp9mo ago
sepp
sepp9mo ago
So it should be like this am i correct?
Pobiega
Pobiega9mo ago
there are more tutorials for both of those things than there are programmers yes, kinda but the only reason I can think of why you would do this is to support unittests, so I'd expect to see interfaces for both the repository and the service
sepp
sepp9mo ago
And register this service on my API layer?
Pobiega
Pobiega9mo ago
its my own personal opinion that N-tier and generic repositories (assuming its over EF Core here) are a waste of time and not a very good pattern
sepp
sepp9mo ago
I have interface for GenericRepository and specific repositories which extends from GenericRepository :/ I have asked to do this in this way that is why
Pobiega
Pobiega9mo ago
right, but your UserService is hard-coupled to your UserRepository
sepp
sepp9mo ago
I am doing a internship for a small company
Pobiega
Pobiega9mo ago
you should be using the interface here IUserRepository etc okay thats fine but you should be requesting the repository interface in your service and storing that, not the concrete implementation
sepp
sepp9mo ago
So I should create a interface too for repositories
sepp
sepp9mo ago
this is the current architecture btw
Pobiega
Pobiega9mo ago
repository, and service too if you wanna go full N-tier :p everything should have an interface, so it can be mocked/stubbed/whatever for tests and each "module" (DAL, Services) should have its own extension method for IServiceCollection like services.AddMeetingAppDAL(); and services.AddMeetingAppServices(); so you can distribute your DI setup
sepp
sepp9mo ago
I am more lost now lol But will consider your advices thank you very much for helping,
MODiX
MODiX9mo ago
sepp
From sepp
React with ❌ to remove this embed.
Pobiega
Pobiega9mo ago
you can go nuts on the interfaces later the idea is that you "inject" all your dependencies and never new it up
sepp
sepp9mo ago
I got stuck again lol, do I need to register dbcontext and repository in my API layer? It feels like nonsense to me since I think that I shouldn't expose DAL layer to API layer
Pobiega
Pobiega9mo ago
yes you need to register it, how else would the service provider be able to resolve them and you're not really exposing the DAL to the API layer just because you are registering it in the service collection before your application even starts See, this is why I hate N-tier architecture. People get so caught up on "following the rules" of the architecture instead of actually thinking about what is going on 😦
sepp
sepp9mo ago
So setting a project reference from API layer to DAL is not a problem right?
Pobiega
Pobiega9mo ago
yes, you can't get away from that
sepp
sepp9mo ago
If I do that everything will be solved in my mind lol I was too worried about seperation :/
Pobiega
Pobiega9mo ago
your entrypoint will always need to reference more or lesss everything because its the entrypoint to your entire program :p
sepp
sepp9mo ago
Oh okay then 😅
Pobiega
Pobiega9mo ago
but yeah, I still suggest you use extension methods for each of your modules, so they can do their own setup in their own packages and you just call that one .AddMeetingAppDAL(); method in your API
S-IERRA
S-IERRA9mo ago
Out of curiosity what would you rather recommend then?
Pobiega
Pobiega9mo ago
Instead of N-tier? VSA, all day long if you "need" an enterprise level architecture at all, VSA is imho the best one
S-IERRA
S-IERRA9mo ago
Can’t really find anything related online apart from something engineering related any possible examples?
Pobiega
Pobiega9mo ago
but honestly, I'm not sure even thats needed. These days I just build everything on a command/handler pattern and then everything else is just whatever I need to invoke my commands ie, am I making an API? I need some controllers/minimal api endpoints am I making a CLI? I need a way to handle console commands -> domain commands Vertical Slice Architecture. The talk by Jimmy Bogard (https://www.youtube.com/watch?v=5kOzZz2vj2o) and Derek Comartins video (https://www.youtube.com/watch?v=L2Wnq0ChAIA) are both excellent These opinions are based on writing and maintaining 3 500K+ LOC enterprise-scale projects. 2 of them started as onion and eventually moved to VSA, and the last one started with command handlers from the start. I had a period where I loved onion and was like "oh yeah this is so good"
S-IERRA
S-IERRA9mo ago
I think a general issue with cs architecture is how much abstraction there is It is generally very easy to get lost in the code especially in the place I worked at I believe they used N tier I’m not sure tho because I’m not too familiar with architectures (junior dev position)
Pobiega
Pobiega9mo ago
but eventually you just have tons and tons of services with 70+ methods, and you must have an interface for everything you make just so it can be mocked...
S-IERRA
S-IERRA9mo ago
Yes that….
Pobiega
Pobiega9mo ago
absolutely
S-IERRA
S-IERRA9mo ago
It just became a pain in the ass Their entire development seemed like a giant waste of time to me Because everything was so so abstract
Pobiega
Pobiega9mo ago
if you move from that to command/handler, each individual method becomes a command and a handler, more or less. At least the ones you call from apis the good thing here is that you can then move these around however you want. We usually group them in namespaces related to what they operate on so instead of 70 methods, you have 70 classes, but they can be split and moved as you see fit
S-IERRA
S-IERRA9mo ago
I believe you ve seen my code how I put logic in the controllers which from what I heard shouldn’t be done any advice on what I could do there
Pobiega
Pobiega9mo ago
and a nice dispatcher with pipeline support means you can easily add cross-cutting concerns as middleware, like transactional security
S-IERRA
S-IERRA9mo ago
Right now since it’s a personal project I’m not gonna bother moving all of it but for future reference
Pobiega
Pobiega9mo ago
or global exception handling depends. If its a smaller project, I'd probably consider using FastEndpoints instead of controllers, and have my logic in the endpoint handler
S-IERRA
S-IERRA9mo ago
FastEndpoints? and no its a larger project will have to look into fastendpoints tho
Pobiega
Pobiega9mo ago
third party library that wraps minimal APIs its very fast and very readable
S-IERRA
S-IERRA9mo ago
oh damn im reading it rn yeah thats alot nice for smaller scale apps
Pobiega
Pobiega9mo ago
it takes a bit of getting used to, but its very nice
S-IERRA
S-IERRA9mo ago
I'm more talking like what you d do with this
S-IERRA
S-IERRA9mo ago
because what I was told at my old work place is to never put logic in the controller rather to abstract it to another service layer which im not too keen on because thats just creating another service for no reason
Pobiega
Pobiega9mo ago
yeah, thats a very common idea exactly there is a potential reason to do it thou
S-IERRA
S-IERRA9mo ago
I understand in terms of re-usability yes
Pobiega
Pobiega9mo ago
to separate HTTP concerns from business concerns
S-IERRA
S-IERRA9mo ago
I see
Pobiega
Pobiega9mo ago
and re-usability for future, like making a CLI invoker for your app
S-IERRA
S-IERRA9mo ago
I se eyeah Though I think alot of companies have gotten too worried with abstraction and stuff like that
Pobiega
Pobiega9mo ago
actual real-world thing: I wrote a CLI that can import patients into our big HL7 E-health platform its a massive fucking webapp, with tons of events and stuff happening when registering patients but we wanted to import a CSV of patients for a new customer
Unknown User
Unknown User9mo ago
Message Not Public
Sign In & Join Server To View
Pobiega
Pobiega9mo ago
so I slapped together a CLI that can run our commands, took me 2 hours
S-IERRA
S-IERRA9mo ago
idk if u saw how amazon switched from micro arch to monolith and it massively improved speed
Unknown User
Unknown User9mo ago
Message Not Public
Sign In & Join Server To View
Pobiega
Pobiega9mo ago
right oh, you have an MVC app that you are adding an API to?
Unknown User
Unknown User9mo ago
Message Not Public
Sign In & Join Server To View
Pobiega
Pobiega9mo ago
right
Unknown User
Unknown User9mo ago
Message Not Public
Sign In & Join Server To View
Pobiega
Pobiega9mo ago
yeah thats when you need that kind of abstraction.. oh well if its laravel you dont share any code anyways you have to re-implement the logic since its two different languages
Unknown User
Unknown User9mo ago
Message Not Public
Sign In & Join Server To View
Pobiega
Pobiega9mo ago
Oh sorry, new person you are doing the API and the "MVC" part both in laravel
S-IERRA
S-IERRA9mo ago
I have another concern with this, for example if I was to return a status type I believe I have to do some weird generic return type of stuff then return the value
Unknown User
Unknown User9mo ago
Message Not Public
Sign In & Join Server To View
Pobiega
Pobiega9mo ago
I thought you were doing the API in C# and the MVC was laravel 😄
Unknown User
Unknown User9mo ago
Message Not Public
Sign In & Join Server To View
S-IERRA
S-IERRA9mo ago
thats if i was to put the logic at service
Unknown User
Unknown User9mo ago
Message Not Public
Sign In & Join Server To View
S-IERRA
S-IERRA9mo ago
not sure how you guys go aboutr that
Pobiega
Pobiega9mo ago
I actually have an example for this. wait a sec
[HttpGet]
public async Task<ActionResult<TodoList>> Get(int id)
{
var query = new GetTodoListQuery()
{
Id = id
};

var result = await _mediator.Send(query);
if (!result.IsSuccess)
{
return result.Error switch
{
NotFoundError notFoundError => NotFound(notFoundError.Message),
ValidationError validationError => BadRequest(validationError.ValidationFailures),
_ => Problem(detail: result.Error.Message, title: "Internal Server Error", statusCode: 500)
};
}

// return _mapper.Map<TodoListDto>(result.Entity);

return result.Entity;
}
[HttpGet]
public async Task<ActionResult<TodoList>> Get(int id)
{
var query = new GetTodoListQuery()
{
Id = id
};

var result = await _mediator.Send(query);
if (!result.IsSuccess)
{
return result.Error switch
{
NotFoundError notFoundError => NotFound(notFoundError.Message),
ValidationError validationError => BadRequest(validationError.ValidationFailures),
_ => Problem(detail: result.Error.Message, title: "Internal Server Error", statusCode: 500)
};
}

// return _mapper.Map<TodoListDto>(result.Entity);

return result.Entity;
}
this combines result types with a command runner
Unknown User
Unknown User9mo ago
Message Not Public
Sign In & Join Server To View
S-IERRA
S-IERRA9mo ago
not a fan of exceptions very slow very annoying to deal with down the line try catching everything
Unknown User
Unknown User9mo ago
Message Not Public
Sign In & Join Server To View
S-IERRA
S-IERRA9mo ago
still, better ways of doing it like this Also ty, why are you using a Mediator there if I could ask
Pobiega
Pobiega9mo ago
I vehemetly disagree with this take
S-IERRA
S-IERRA9mo ago
my old company did this made me wanna kms
Pobiega
Pobiega9mo ago
global exception handler means your entire codebase must use a uniform exception set, or be made aware of HTTP specifics both are bad because I need a command executer. Mediator is perfectly fine.
S-IERRA
S-IERRA9mo ago
I see
Pobiega
Pobiega9mo ago
This is the source-genned version, not that you can see that 😄
Unknown User
Unknown User9mo ago
Message Not Public
Sign In & Join Server To View
Pobiega
Pobiega9mo ago
yeah its a bit of an eye opener
Unknown User
Unknown User9mo ago
Message Not Public
Sign In & Join Server To View
Pobiega
Pobiega9mo ago
granted, we still have a global exception handler just in case an exception leaks but it just logs and returns 500
S-IERRA
S-IERRA9mo ago
If I oculd ask what you mean by http specifics or uniform exception set
Pobiega
Pobiega9mo ago
fuck I'm late, I'll be back in 20
Unknown User
Unknown User9mo ago
Message Not Public
Sign In & Join Server To View
S-IERRA
S-IERRA9mo ago
I see
Unknown User
Unknown User9mo ago
Message Not Public
Sign In & Join Server To View
Pobiega
Pobiega9mo ago
Back that example uses RemoraResults which is nice and easy to use, but has a tradeoff - it doesn't explicitly list all error types in its type declaration, like a OneOf would but you can use whatever you prefer in this case, it looks like
public class GetTodoListQueryHandler : IRequestHandler<GetTodoListQuery, Result<TodoList>>
{
public async Task<Result<TodoList>> Handle(GetTodoListQuery request, CancellationToken cancellationToken)
{
if (request is null)
{
return Result<TodoList>.FromError(new ArgumentNullError(nameof(request)));
}

// validate query
var validator = new GetTodoListQueryValidator();
var validationResult = await validator.ValidateAsync(request, cancellationToken);

if (!validationResult.IsValid)
{
return new ValidationError(validationResult.Errors);
}

var list = FakeDB.Instance.TodoLists.FirstOrDefault(x => x.Id == request.Id);

return list ?? Result<TodoList>.FromError(new NotFoundError());
}
}
public class GetTodoListQueryHandler : IRequestHandler<GetTodoListQuery, Result<TodoList>>
{
public async Task<Result<TodoList>> Handle(GetTodoListQuery request, CancellationToken cancellationToken)
{
if (request is null)
{
return Result<TodoList>.FromError(new ArgumentNullError(nameof(request)));
}

// validate query
var validator = new GetTodoListQueryValidator();
var validationResult = await validator.ValidateAsync(request, cancellationToken);

if (!validationResult.IsValid)
{
return new ValidationError(validationResult.Errors);
}

var list = FakeDB.Instance.TodoLists.FirstOrDefault(x => x.Id == request.Id);

return list ?? Result<TodoList>.FromError(new NotFoundError());
}
}
By "uniform exception set" I mean that every single exception that might leak all the way to the global exception handler, the handler must be aware of and know how to map to a HTTP response code "UserNotFoundException" => 404 is easy enough, but what about "InvalidOperation"? and if you ever add a new exception, you must remember to map it okay, but lets say you see how fragile that is, so you go with a system where you limit the number of exceptions you're allowed to throw to a fixed set: NotFoundException BadRequestException etc well, now your entire codebase is HTTP aware is this a problem? Up for you to decide, but imho its not very nice when my domain logic is concerned about what http status code should be thrown
S-IERRA
S-IERRA9mo ago
Pobiega sorry to cut off topic any chance you could look into https://discord.com/channels/143867839282020352/1150807212017602742 having some issues with EFC maybe you know the anwser