In a layered architecture, how can the Application layer know about Web URLs?
Asked Answered
M

2

6

I'm currently working on a .NET 5 app that is using a layered architecture (Web / Application / Infrastructure / Domain). If I am to follow the onion/clean architecture pattern, the dependencies should flow in one direction only, e.g:

Web -> Application -> Infrastructure -> Domain

I now find myself needing to send several emails from the Application layer containing specific front-end URLs. This means that the Application layer will know about the Web layer, breaking the dependency flow.

A sample use case flow would be:

  1. User makes a request, gets handled by a controller in the Web layer
  2. Controller calls a handler on the Application layer
  3. The Application layer uses an email service from the Infrastructure layer to send an email

On step #3 I'm in the Application layer but need Web URLs to construct the email body.

How can I solve for this issue?

Mush answered 2/7, 2021 at 14:53 Comment(5)
URLs are effectively just data. That is different than having a dependency on Web unless you can only obtain them by calling into WebChabazite
If you need information from an outer layer in an inner layer then move the information from the outer layer to the inner layer with the call you do from the outer layer to the inner layer.Stull
you either need to pass it down through the layers or store it in the appsettings.json for more info check thisSurgy
@Chabazite I guess so, although it would be nice to be able to resolve a Controller URL from it's method just in case the route changes, so everything continues to work as expected.Mush
@Stull I also thought of that but sometimes it seems unoptimal to pass data down through several methods that won't use it. Will give it some thought tho, thanks.Mush
K
1

I've recently solved this problem within my organization. In our case we have an API "market place" used by the company as a whole, then a reverse proxy used by closely integrated clients, and finally an internal load balancer for the API containers.

That's 3 layers of URL knowledge that my Web and App layers shouldn't know about (even at the Web layer it shouldn't know this because that would make our web layer have more than one responsibility rather than just be a router (e.g. via Mediatr)).

Using Rewriters in the Infrastructure

This is what Z. Danev's answer is all about. This works, but you must maintain all the rules for each of these layers, and each of those rewrites may add overhead. Also, those rules could get tricky depending on the complexity of the data you return.

It is a valid solution though. Depending on your organization this may be an easy thing, or it may be a hard one because it's maintained by other teams, need work tickets, and so on to get the job done.

Well, if you can't or don't want to do that, then...

Application Layer Dependency Inversion and Patterns

Disclaimer: This solution works great for us, but it does have one drawback: at some level, you have to maintain something that knows about the layers above. So caveat emptor.

The situation I described above is roughly analogous to your problem, though perhaps more complex (you can do the same but simplify it). Without violating your architectural principals you need to provide an interface (or more than one) that can be injected into your application layer as an application service.

We called ours ILinkBuilderService and created a LinkBuilderService that itself can be wired up through a DI container with individual ILinkBuilder implementations. Each of these implementations could be a MarketPlaceBuilder, a GatewayBuilder, etc. and will be arranged according to a Chain of Responsibility and Strategy patterns from outermost proxy to innermost.

In this way, the builders inspect the web context (headers, request, etc.) to determine which one should handle the responsibility of building links. Your application layer (e.g. your email sender) simply calls the link building service interface with key data, and this is used to generate client-facing URLs without exposing the application layer to the web context.

Without going into too many details, these builders inspect headers like X-Forwarded-For, custom headers, and other details provided by proxies as the HTTP request hits each endpoint. Chain of Responsibility is key, because it allows the application layer to generate the correct URL no matter at which layer the request originated from.

So how does this not break the one-way flow?

Well, you push these builders one-way down into your application layer. Technically, they do reach back up to the web layer for context, but that is encapsulated. This is ok and does not violate your architecture. This is what dependency inversion is all about.

Kanpur answered 2/7, 2021 at 16:12 Comment(2)
Hey @Kit, I appreciate your detailed response. You mean I would define the implementations of the ILinkBuilderService inside the Web layer, so that i can access HttpContext and build my URLs? Do you find that many application services / Mediatr requests now require these services as payload to the respective handlers in the application layer? I guess that's a fancier / more flexible way than passing down a specific URL as i now have access to build any i need.Mush
Yes the implementations would be in the web layer. They could be passed at runtime via Mediatr or simply injected into application layer after being registered with an IoC container. The builders would take parts of the context (e.g. outward facing host, path, etc.) and from that be able to build related URLs. For example if you have https://apigateway.com/v1/orders/2 you could build https://apigateway.com/v1/orders/items or ...v1/users/joe even though your API really lives at http://random.host.com:8000/v1. This avoids passing URLs to services; you're passing context-aware builders.Kanpur
L
0

Consider configuring "well known urls" at the web infrastructure level (gateway or load balancer for example) so you can have "mycompany.com/user-action-1" in the email and that will translate to the proper endpoint of your web app.

Linis answered 2/7, 2021 at 15:5 Comment(2)
Do you mean it's the lower level that defines the URLs and the Web layer that "implements" them? So /user-action-1 would be an Application-level concept that the Web layer should find a URL for?Mush
yes, I'm suggesting that the urls are infrastructure concern. think about having dev/test/prod environments with different urls, load balancers, dns servers, ssl, ...Linis

© 2022 - 2024 — McMap. All rights reserved.