Skip to main content
25 events
when toggle format what by license comment
Sep 19, 2024 at 23:50 comment added Flater @user1079002 "there so right or wrong way to do it" There is, but it requires much more consideration than your question contains.
Sep 19, 2024 at 14:13 comment added user1079002 I understand, it's not set in stone and there so right or wrong way to do it. Makes sense, thanks
Sep 19, 2024 at 6:31 comment added Flater @user1079002: This isn't meaningfully answerable in a comment. The question effectively touches on the core mechanic of how you separate layers and what it means for one layer to define a particular contract/interface. This is not a short question to answer, it leads down a rabbit hole of understanding the core premise of your architecture and learning how to apply it. I don't know your business requirements, nor what these values mean, nor what is considered to be relevant to protect against for future maintainability. Ask your architect or lead.
Sep 19, 2024 at 5:08 comment added user1079002 thanks for the patience, I think my question is simpler than it looks like. I am talking about retrieving data from 3rd party APIs. Either application layer will define interface with return model which has one of the property string and then map that string to the domain enum or it's gonna define return model with one of the property type enum and then the whoever implements interface map string to the enum. Whoever maps string to the enum will have to know that 2 different strings 'tradeable' and 'active' map to the same enum value Active. Which one is better in the long run?
Sep 19, 2024 at 4:46 comment added Flater @user1079002: I get the feeling that you are confused about the bigger picture here as you seem to be talking about a persistence-defined interface and how it would (or would not) violate "rules of inverted dependencies", which are mutually exclusive scenarios and make no sense together. This isn't something I can meaningfully diagnose or expand upon in a comment. I suggest you revisit your understanding of these terms and the guidelines on whatever architecture it is that you're actually using.
Sep 19, 2024 at 4:44 comment added Flater @user1079002 "That means that data provider, which is in infrastructural layer, must know little bit about business logic?" No. If persistence defined the interface (and related models), then the interface (and related models) expresses a persistence concern, and it is the application's responsibility of ingesting this and converting it into an application-level concern. Effectively, whoever doesn't define the interface (and related models) therefore has to map its internal concerns to that interface (and related models).
Sep 19, 2024 at 4:18 comment added user1079002 I see and just one follow up question: whoever implements the interface, in my case data provider module, must also map to the interface method return type, right? That means that data provider, which is in infrastructural layer, must know little bit about business logic? For example, data strings 'active' and 'tradeable' both are mapped to enum SecurityStatus::Active and that's ok? It doesn't break rules of inverted dependencies or similar?
Sep 19, 2024 at 4:06 comment added Flater @user1079002: At its core, if the domain defines the interface (as is the case with inverted dependencies), then the domain (which should not depend on anything external) must invariably also define the models that are used in the interface. For normal dependencies (e.g. N-tier architecture), persistence defined the interface, and therefore persistence must either define the models or have access to pre-existing models (which the consumer of said interface must then also have access to).
Sep 19, 2024 at 4:04 comment added Flater @user1079002 DTO is a vague term that has no connotation on what layer it lives on or what its purpose is. Any object that is used to transfer data is a DTO, whether it's a persistence entity, anemic domain model, or application model. But, to answer the core of your question, whoever defined the interface must also invariably have access to (if not be) the one who defined any models that are using in the interface in question.
Sep 19, 2024 at 3:57 comment added user1079002 so we don't define dto as return type for interface in inverted dependency in application layer? I thought application layer defines interface IDataProvider and dto StockDataDto as return type because that what application needs and then data provider needs to provide that dto. But dto needs to be defined internally in data provider module and then it maps to what application layer needs. And application layer instead of defining dto actually defines the domain model data provider needs to provide? and that is the return type of IDataProvider interface?
Aug 24, 2023 at 0:18 comment added Flater @RafaelEyng: Consider what we're talking about now, and what the actual question that was being asked. This is a generalized question, but you repeatedly push back on provided advice by pointing at reasons related to your specific use case. This does not match. You need to analyze whether a generalized answer is actually applicable to your scenario; which requires a level of deep functional and technical analysis that simply cannot be done over StackExchange's intentionally restrictive Q&A format.
Aug 24, 2023 at 0:16 comment added Flater @RafaelEyng: The implementation in Infrastructure is not limited to being a single class. You could have an PaymentRepository : IPäymentRespository (whose interface is independent of payment providers) and a StripePaymentProvider and GooglePaymentProvider which are relied upon by PaymentRepository's implementation. But the larger question is first to consider if these providers are an implementation detail or a concrete business requirement, because if it's the latter, then any advice on needing to abstract such an implementation is inherently moot. You need analysis here.
Aug 24, 2023 at 0:06 comment added Rafael Eyng The best I can think of is to have my adapter have a list of all the implementations of the interface, and each implementation returns its own enum value in a paymentProvider() method. Then depending on the parameter that determines the payment provider, my adapter picks the correct of the multiple implementations (of course, using it through the interface). That's the only way I can see my adapter not having some sort of if (paymentProvider == 'stripe') { /* pick this implementation, else pick that */ }.
Aug 24, 2023 at 0:03 comment added Rafael Eyng Thanks, Flater. I think you are missing my point. Something inside my adapter layer needs to know "for this payment, I need to use the Stripe implementation or the GooglePay implementation", even if the 2 implement the same abstract interface. So at some point my adapter needs to know "if some parameter is X, I go for the Stripe implementation of that interface, otherwise I go for the GooglePay implementation". I don't see how it can do it without acknowledging that there are 2 different implementations, thus leaking the knowledge of what different "drivers" exist to the adapters layer.
Aug 23, 2023 at 22:12 comment added Flater @RafaelEyng "something, at some point, needs to know the specifics about Stripe" Yes it does. That something is the Infrastructure implementation, but not its interface. The implementation has to translate (map) any Stripe specifics to/from your interface models. Your interface should only be designed used models that you designed yourself. These might resemble the Stripe models, or they might not. What's important is that when Stripe decided to change its model, that your codebase is able to maintain its own interface/models without needing to change them (barring breaking changes)
Aug 23, 2023 at 22:09 comment added Flater @RafaelEyng: You've introduced the concepts of adapters and drivers here, which were not part of the original question. This requires elaboration. I surmise that "drivers" are the infrastructure implementation and "adapters" is your API, based on what you've said?
Aug 23, 2023 at 13:27 comment added Rafael Eyng (...) Also, the Stripe webhook is totally Stripe-specific. It receives Stripe data from Stripe. So I think it should probably live in the drivers layer, even though it is arguably similar to a controller (which should live in the adapters layer), in the sense that it receives and handles requests. But maybe the difference here is that the requests handled by controllers are agnostic to details while a webhook is by definition attached to a specific service.
Aug 23, 2023 at 13:25 comment added Rafael Eyng Thanks. I don't disagree with your point. But I do think something, at some point, needs to know the specifics about Stripe. In my case, there are at least 2 points. PaymentRepository on the adapters layer needs to know if it should call the underlying Stripe implementation to create a payment, or the Google Pay one. And those details should arguably be in the drivers layer, but somehow the adapters has to acknowledge multiple implementations exist and distinguish between them. (...)
Aug 23, 2023 at 1:28 comment added Flater @RafaelEyng: I don't know Stripe, and the fact of the matter is that I shouldn't need to know it in order to help you design your codebase. Instead of basing your design based on what Stripe has, design your application based on what you need it to do and then figure out how to implement those needs using a concrete implementation (in this case it happens to be Stripe).
Aug 23, 2023 at 0:07 comment added Rafael Eyng (...) If I implement PaymentRepository at adapters layer, how can it see the Stripe client, that is in the infrastructure layer? Or if I put Stripe webhook in the infrastructure layer, it can access Stripe client in the same layer, but not PaymentRepository to fall into the general flow. But if I put it into the adapters layer, the problem is reversed. The webhook now can access the PaymentRepository, but not the Stripe client. Unless I make a huge interface to cover what I need from Stripe, but when I add the next payment service, fitting the interface would be a nightmare.
Aug 23, 2023 at 0:02 comment added Rafael Eyng Thanks, what you said makes sense. I'm still unsure about my case, with Stripe. While I want to have general payment use cases that can call some PaymentRepository or PaymentProvider (implemented at the adapters layer?) that can abstract Stripe away, I'm not sure what to do with my need of having Stripe-specific webhooks. At some point in the webhook flow I am able to make it webhook fall into the general case the PaymentProvider implements, but before that, I need the webhook to have access to Stripe-specific calls and types. So that leaves me clueless about where to put the webhook (...)
Aug 22, 2023 at 23:13 comment added Flater @RafaelEyng: If your API added a completely new thing, and you want to use it, it is inevitable that you must redevelop. However, if the API changes something and you don't want to change your business logic, then all you have to do is rewrite the Infrastructure mapping in a way that the new API maps to your original model. You can't avoid needing to make changes to Infrastructure when your external components go through breaking changes; but you can contain the redevelopment to the Infrastructure project and nowhere else in your codebase - that's the purpose of designing these interfaces.
Aug 22, 2023 at 23:10 comment added Flater @RafaelEyng: On the face of it you raise a valid point, but you have subtly picked two conflicting stances. If your persistence layer is not the one defining this interface (= the inverted dependencies case), then the interface (and its models) should not be designed based on persistence considerations (which includes knowing what the external API provides and contains). You would be designing the interface and the models based on what your application needs, not based on what the API offers.
Aug 22, 2023 at 19:15 comment added Rafael Eyng What if, in the inverted dependencies case, the service to be abstracted is very large? Let's say I'm consuming a 3rd party that has a ton of functionality, thus a huge API surface, considering classes, functions, input/output types. Should I abstract all of that into my own interface that lives in an inner layer? It feels like my abstraction will be extremely brittle and tied together with the 3rd party.
May 31, 2021 at 9:13 history answered Flater CC BY-SA 4.0