• Post Reply Bookmark Topic Watch Topic
  • New Topic
programming forums Java Mobile Certification Databases Caching Books Engineering Micro Controllers OS Languages Paradigms IDEs Build Tools Frameworks Application Servers Open Source This Site Careers Other all forums
this forum made possible by our volunteer staff, including ...
Marshals:
  • Campbell Ritchie
  • Liutauras Vilda
  • Junilu Lacar
  • Jeanne Boyarsky
  • Bear Bibeault
Sheriffs:
  • Knute Snortum
  • Tim Cooke
  • Devaka Cooray
Saloon Keepers:
  • Ron McLeod
  • Stephan van Hulst
  • Tim Moores
  • Tim Holloway
  • Carey Brown
Bartenders:
  • Piet Souris
  • Frits Walraven
  • Ganesh Patekar

Pattern for a inheritance / composition with different behaviour

 
Greenhorn
Posts: 5
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Im currently designing a solution where I need some input. Im programming in Java.

There is an incomming request, containing personal information, and a list of products that the person wants prices on.
There is multiple different input API`s. But every API validates and transforms the input to PriceRequest object.
This PriceRequest object contains the personal information, and also the list of Products the person wants prices on.
Each Product can be one of about 8 different Products. And each of those 8 different Products have different properties that the customer is setting for customizing the product. They share no common properties expect they all are a Product.

When processing the incomming request we pass the PriceRequest to a PriceService.getPrice.
The PriceService validates customer and does a number of required steps.
Then for each Product in the PriceRequest, we must do product specific validation, apply product specific rules and call an external pricing service. The external pricing service is different for each Product. So again, the Products share not much, but they do share a common way of being processed, but the processing differs for each Product.

So im thinking about how to best design this in our system.
At first i was thinking that each Product can inherit from a BaseProduct, and the BaseProduct has abstract methods for validating product , running product rules and getting price from external service. This would make code simple, as PriceService can then for each product just call these methods in the correct order.
But im not really liking this, because the Product and the PriceRequest are actually just immutable DTOs, and it dont feel right to but business logic into them.
Also it wouldnt feel natural to inject the necessary dependencies to external services into the Product.

Next i was thinking that in PriceService i can, for each product, have if-instanceof statements and hard code different instances to do different kind of things. But now it feels like im putting to much into the PriceService, also it feels a bit akward.

Next i was think Template Method Pattern, creating an abstract ProductService with abstract methods, and then implementing product specific ProductServices. This feels better, but still it would require me to have those ugly if-instance-of statements inside the PriceService and delegating to the corrrect product specific service.

Does anyone have any better ideas for me? I would be really happy if anyone would have some tips on a Pattern or way of doing this that would be more natural and better suited.
Or just confirm if im on the right track or not.

Happy for all feedback!
Let me know if i need to clarify something.


 
Marshal
Posts: 14053
234
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Welcome to the Ranch!

Eric Graysman wrote:
When processing the incomming request we pass the PriceRequest to a PriceService.getPrice.
The PriceService validates customer and does a number of required steps.


Allowing the PriceService to know about PriceRequest does not seem like a good design choice to me. A PriceService would have to know about Products; it doesn't need to know about PriceRequests though. I always watch for the semantics in a design. In my experience, when semantics are awkward or don't make sense, there's probably an underlying problem in the design that's causing it. In this case, why would you pass a PriceRequest to a PriceService.getPrice() method? I imagine code that looks something like this:

I can see why you might think this makes sense. I'd guess you're probably thinking it represents this idea: "I need to ask PriceService for some prices so I'll give it a PriceRequest because that's where it can find all the information it needs."

If you examine that closely, however, there's a problem of implicit knowledge of implementation details because while you may know that a PriceRequest contains information about Products and Customer, others may not. The code doesn't explicitly say anything about that relationship between PriceRequest and Products/Customer. Anyone not familiar with those details will need to dig into PriceRequest to figure that out. To me, that's a design smell.

Consider this alternative:

With this design, getPrice(Product) or getPrices(List<Product>) has very clear semantics of "get the price for this Product" or "get the prices for these Products" -- there's no implicit knowledge needed there because everything is expressed by the code. If the PriceService needs the Customer information as well, then you can design the API this way:

This would make it clearer to me that the getPrice() depends on the Product and Customer.
 
Junilu Lacar
Marshal
Posts: 14053
234
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
You're right to think that multiple instanceof checks are smelly. It's usually a sign of improper use of inheritance and polymorphism.
 
Sheriff
Posts: 24654
58
Eclipse IDE Firefox Browser MySQL Database
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator

Junilu Lacar wrote:This would make it clearer to me that the getPrice() depends on the Product and Customer.



But if it turned out that getPrice() also depended on the delivery date and the warehouse the product(s) were shipped from and some special deals agreed to by the sales person, you might change your mind, right?
 
Junilu Lacar
Marshal
Posts: 14053
234
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I find that making design decisions based on textual descriptions alone a very tricky proposition. Read the essays by Jack Reeves on Code as Design where his basic premise is that code is really detailed design and that writing code is really a design activity. As a practitioner of Test-Driven Development, I find this to be absolutely true.

Kent Beck says this: "When I write tests I'm not just writing tests. I'm making API decisions, I'm making analysis decisions and the tests just happen to be the notation that I use to record those decisions."

Tests are also a great way to experiment with different design options. When I can see code that expresses my design well and tells a coherent and cohesive story, then I feel better about my design choices. Without code, it's anybody's guess whether a decision is good or not. I often like to say "Ideas always seem great while they're just floating around in your head. Only when you see them expressed in code can you tell if ideas are actually good or not."
 
Junilu Lacar
Marshal
Posts: 14053
234
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator

Paul Clapham wrote:

Junilu Lacar wrote:This would make it clearer to me that the getPrice() depends on the Product and Customer.



But if it turned out that getPrice() also depended on the delivery date and the warehouse the product(s) were shipped from and some special deals agreed to by the sales person, you might change your mind, right?



Sure. Passing in more than three or four parameters to a method is another smell I watch out for.

Names are also important. If there were multiple factors that affected pricing, it might make more sense to pass getPrice() something like a PricingContext or ProductContext object. The "context" part of the name implies that there are multiple factors that can affect pricing. On the other hand, "request" seems to speak more about the source or manner in which the information is coming. It may be a subtle nuance but I think it's something that can either add to or ease cognitive load.
 
Junilu Lacar
Marshal
Posts: 14053
234
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator

Eric Graysman wrote: ... But im not really liking this, because the Product and the PriceRequest are actually just immutable DTOs, and it dont feel right to but business logic into them.
Also it wouldnt feel natural to inject the necessary dependencies to external services into the Product.


Make sure you are clear on the difference between DTOs vs Domain Objects -- DTOs have their use but people often misuse them. I have a feeling you are misusing them. See https://martinfowler.com/bliki/LocalDTO.html
 
Eric Graysman
Greenhorn
Posts: 5
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Thanks for all your helpful input!
Ok, so let me first adress a couple of questions:

Junilu Lacar wrote:

Eric Graysman wrote: ... But im not really liking this, because the Product and the PriceRequest are actually just immutable DTOs, and it dont feel right to but business logic into them.
Also it wouldnt feel natural to inject the necessary dependencies to external services into the Product.


Make sure you are clear on the difference between DTOs vs Domain Objects -- DTOs have their use but people often misuse them. I have a feeling you are misusing them. See https://martinfowler.com/bliki/LocalDTO.html


Thanks for this tip. I just read the article by Martin Fowler, and the reason we are doing this is the same reason he states in his last paragraph. "When there is a significant mismatch between the presentation layer and the underlaying domain-model". For us, the presentation-layer is dictated by WSDLs of witch we have no control over, so we need to map over to a DTO with a guaranteed valid and immutable state.
This is our PriceRequest object, and the collection of Products that it contains.

As another one pointed out, PriceRequest might not be the best name of this object, maybe PricingContext is more suitable.

Yes, the price depends on the Customer and the Product, so it would make sense to have those two objects as input to the pricingService. But we also need another step as you will see below. (See step 3)

First of all, lets assume we have 4 different Products. WindowProduct, DoorProduct, BalconyProduct and SofaProduct. (This is not real products btw. just using random names for anonymity). Each of these products naturally have really different attributes/variables, and they are all custom products.

Now let me rewind back a bit and explain briefly our current flow:
1. Incomming request to API. Authorization, input validation and transformation to PriceRequest happens here. PriceRequest is immutable and contains enough information to fetch the Customer, although not the Customer object itself. It also contains a List of something that extends BaseProducts.
2. API layer calls Business layers PricingService.getPrice(priceRequest).
3. Inside PricingService.getPrice: Fetch Customer and do customer validation-checks
4. Still inside PricingService.getPrice: Then for-each product do: product-specific-stuff (input is product and customer). And do fetch price from external service (input is product and customer)
5. Still inside PricingService.getPrice: Collect all prices and return PriceResponse to API-layer
6. API layer transform PriceResponse to API presentation model.

Currently the PricingService is our Orchestrator, that orchestrates which steps is necessary to fetch the price. The PricingService will fetch the Customer, validate the customer, and orchestrate which product specific rules are run and what external service is called.
Im specially interested in how to best design our step 3 and 4.

We need someone to fetch the Customer and validate the Customer based upon the PriceRequest / PricingContext. Maybe PriceService is not the right name?
We do have a CustomerService that can fetch and validate the Customer, but we need someone to orchestrate the calls to this service.
We need to design some inheritance / composition aroundt the Products so we can iterate them and do product-specific checks and product-specific price fetching in an elegant way. Preferably not using instance-of, and preferably not by adding business logic into the Product objects.

The best i have come up with so far is:
1. PriceService.getPrice(PricingContext/PriceRequest) - Orchestrator
2. Inside PriceService.getPrice, fetch Customer and validate customer
3. Inside PriceService.getPrice, for each product do instance-of checks and call product specific service e.g. WindowProductService.productValidation(product) and WindowProductService.getExternalPrice(customer, product).

This feels sub-optimal.

I would prefer to remove the instance-of checks, but i cant see how at the moment. The only way i can see is to have the specific Products themself have a reference to the correct productValidationService and the correct externalProductPriceService. But in order to achive that i would have to put that into an Object i have though of as a DTO. But maybe its not really a DTO? Maybe thats ok?
 
Junilu Lacar
Marshal
Posts: 14053
234
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
That description just makes me think "God class" -- you're saying "still inside PricingService.getPrice()" waay too much, even for things that really have to do with Customer, like customer validation.

The other thing that jumps out at me about your description is this: "How the hell are you going to test that?" and I'm guessing the answer is going to be something along the lines of "Well, it's complicated."  The way you described it, you bet your bottom it's going to be complicated to test. I'm guessing that currently, any testing you're doing needs a full environment, maybe submitting requests through PostMan or a similar tool, looking at log files, and a whole lot of other tedious and time-consuming things. Is that a good ballpark estimate or did I hit it right on the money and out of the park?

Worst case: you answer "Well, yeah, about testing... funny you should ask" and just go  ¯\_(ツ)_/¯
 
Eric Graysman
Greenhorn
Posts: 5
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator

Junilu Lacar wrote:That description just makes me think "God class" -- you're say "still inside PricingService.getPrice()" waay too much, even for things that really have to do with Customer, like customer validation.

The other thing that jumps out at me about your description is this: "How the hell are you going to test that?" and I'm guessing the answer is going to be something along the lines of "Well, it's complicated."  The way you described it, you bet your bottom it's going to be complicated to test. I'm guessing that currently, any testing you're doing needs a full environment, maybe submitting requests through PostMan or a similar tool, looking at log files, and a whole lot of other tedious and time-consuming things. Is that a good ballpark estimate or did I hit it right on the money and out of the park?



Yes the PricingService might be a God class, so im happy to hear other suggestions on how to design it
But its not really doing anything but orchestrating the flow of calls to other components. So it does not contain any other logic than that.
How would you design it?

As for testing, its not been built yet, still just in early design/testing/proof of concept phase so thats why i need some solid advice
But im planning that each of the components called by the PricingService can be tested in isolation, and the api layer can be tested in isolation. But to test it the PricingService in full with all its dependencies there would need to be loaded quite a bit of the context yes.

 
Junilu Lacar
Marshal
Posts: 14053
234
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
What I would try, if I were in this situation, is not use the DTOs in the Service Layer. Once I have the information transferred from the outside using a DTO, I would create rich domain objects out of each DTO. I might have a Factory that can take DTOs and spit out the smarter Domain Objects that know how to perform all those things you keep saying "PriceService.getPrice() does this and that" about.

I'm guessing this PriceService.getPrice() method is at least a few hundred lines long. Am I right? If I am right or if that's a gross underestimation, then you need to refactor and delegate some of the responsibilities that have been jammed into that getPrice() method.
 
Junilu Lacar
Marshal
Posts: 14053
234
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator

Eric Graysman wrote:
As for testing, its not been built yet, still just in early design/testing/proof of concept phase so thats why i need some solid advice


Yeah, so I have very strong opinions about doing Big Design Up Front. For me, that's a recipe for disaster. I think I already cited Jack Reeves and his essays on how Code is Design. I strongly encourage you to read that. You might also get some ideas from reading up on Eric Evans' "Domain-Driven Design"

Testing is a crucial element of the design process. If you wait until the end when you have thousands of lines of code before you write a single line of test code, it's too late and you've probably made all kinds of mistakes. At that point you will be caught in the Sunk Cost Trap and will do everything you can to patch it up to make it work.

Heed John Gall's warning:

John Gall wrote:A complex system that works is invariably found to have evolved from a simple system that worked. A complex system designed from scratch never works and cannot be patched up to make it work. You have to start over, beginning with a working simple system.


For me, testing every step, every decision, every line of code as it is written and immediately refactoring when you detect a problem is the only sensible, responsible, and economically viable way of designing software. It's a series of experiments and continuous process of learning, mostly from mistakes. Hemming and hawing about your design options without actually experimenting with your ideas using tests is a sure way to find yourself in a bottomless pit of despair.

Sorry to paint such a gloomy picture but I've seen this kind of thing happen over and over in my thirty plus years as a software developer. It's a problem that every new generation of developers seems to have to deal with.
 
Junilu Lacar
Marshal
Posts: 14053
234
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator

Eric Graysman wrote:Yes the PricingService might be a God class, so im happy to hear other suggestions on how to design it
But its not really doing anything but orchestrating the flow of calls to other components. So it does not contain any other logic than that.
How would you design it?


Well, I'd start with names. If PricingService is, as you say, only orchestrating the flow of calls to other components, it sounds to me like it's a Controller of some sort. That's what controllers do. They know the flow and they know about dependencies between different parts of the system. So I'd separate the flow control logic out to a PricingRequestController or some similarly-named class. Then I'd write my first test.


Then I'd see if that test code told a good, sensible story. If not, then I'd look at a different design option and experiment with that.

Assuming I feel good so far, I'd write another test, to extend the story told by the tests so that we're testing the next step in the process flow. And so on and so forth, over and over again, experimenting, learning, evaluating options, then either continuing or pivoting to a different design option. All the while, I have tests that tell me everything I believe so far about the system and how it works is correct (assuming my tests are all passing).
 
Junilu Lacar
Marshal
Posts: 14053
234
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator

Eric Graysman wrote:But to test it the PricingService in full with all its dependencies there would need to be loaded quite a bit of the context yes.


This is absolutely NOT true. If you're creating a good design, it MUST be testable and each part of the design must be testable in isolation. Sure, you'll need to perform integration testing and end-to-end system testing in a full environment at some point but to rely on that in the end while you blindly trust your decisions as you build up huge chunks of the system without tests is, sorry to say, the pinnacle of hubris and folly. Learn from the mistakes that hundreds of thousands have made. Always test your designs as you incrementally build them out.

And for heaven's sake, accept the fact that you're going to make a lot of mistakes along the way and embrace it. Find a way to deal with your mistakes by educating yourself on different code and design smells and how to mitigate or eliminate them. You're already off to a good start because from the questions you've been asking, you are not totally clueless about code and design smells. If you haven't spent any significant time studying them then take heart because it means that you have an innate ability to sense when something is not quite right. But do consider taking a more incremental and experimental approach to design. I think it's your best bet if you plan on succeeding.
 
Eric Graysman
Greenhorn
Posts: 5
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator

Junilu Lacar wrote:

Eric Graysman wrote:Yes the PricingService might be a God class, so im happy to hear other suggestions on how to design it
But its not really doing anything but orchestrating the flow of calls to other components. So it does not contain any other logic than that.
How would you design it?


Well, I'd start with names. If PricingService is, as you say, only orchestrating the flow of calls to other components, it sounds to me like it's a Controller of some sort. That's what controllers do. They know the flow and they know about dependencies between different parts of the system. So I'd separate the flow control logic out to a PricingRequestController or some similarly-named class. Then I'd write my first test.


Then I'd see if that test code told a good, sensible story. If not, then I'd look at a different design option and experiment with that.

Assuming I feel good so far, I'd write another test, to extend the story told by the tests so that we're testing the next step in the process flow. And so on and so forth, over and over again, experimenting, learning, evaluating options, then either continuing or pivoting to a different design option. All the while, I have tests that tell me everything I believe so far about the system and how it works is correct (assuming my tests are all passing).



Thanks! I really appriciate your input This is a good start. I think PricingRequestController is a great name.
I completly agree with you on the testing bit. I will try your suggestions around this and see how i manage.
Im also chewing a bit on your rich domain model stuff from above. Maybe the DTOs could really just be Domain objects. And the API can translate directly to domain objects. That does sound kind of good. Altough im a bit afraid that would make the domain objects really big with lots of responsibility. I must read a bit more about this subject to understand it i think.
 
Eric Graysman
Greenhorn
Posts: 5
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Ok so I have read a bit more since yesterday about Anemic vs Rich domain models, and Application Services vs Domain services. And Controllers.

Now there seems to be quite a bit of confusion and differences in this regards. Some would say my "orchestrator" is an application service. Others would say its an Controller. My personal view is tainted by the fact that ive been doing MVC for many years in the old days. So for me a Controller is an MVC thing.
What would you say is the reasons behind you wanting it to be called a Controller and not an Application Service?
 
Junilu Lacar
Marshal
Posts: 14053
234
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
It could very well be an application service. As I was saying before, ideas are great when they're floating around in your head or even when you lay them out in textual descriptions. But the devil is in the details so it's only when you see the ideas expressed as code that you can truly judge whether an idea is good or not.

I'd encourage you to stop hemming and hawing and get on with experimenting by writing some actual code. Look at how much time you've spent just hand wringing. You could have spent that time coding out a design and seeing for yourself if it works for you or not.

Again, you're going to make mistakes. In all likelihood, you're going to make a lot of mistakes as you go. That's all right. It doesn't matter if you don't get it right up front. It only matters that you get it right in the end, or mostly right at least. The only way to get there is to keep screwing up and recognizing when you did, then fixing things. That's what software design is all about.
  • Post Reply Bookmark Topic Watch Topic
  • New Topic
Boost this thread!