Product class responsibilities - oop

In my company we have a very specific pricing strategy:
Every Product in our catalog has a baseUsdPrice, for example product Foo has base USD price of 9.99$.
That does not necessary mean that will be your you'll be paying 9.99$. We check your country for price exceptions - for example in GB Foo costs 8.99$
Further more you can choose a currency you want to pay - if you're in GB you can pay in either USD (mentioned 8.99$) or your local currency (in this case GBP).
If you choose to pay by your local currency we calculate an equivalent of 8.99$ to british pounds based on fixed price matrix (for example here that'll be 3.99£)
This the price you pay.
How should I design my Product aggregate root in DDD manner to be clean, cohesive, decoupled and easy to change?
Should paymentPrice be calculated by domain service and its result put as a part of Product aggregate? If so that means my ProductRepository will have methods like product(productId, countryId, currency)
Should I put all calculations in domain service class PaymentPriceCalculator and use visitor pattern like getPaymentPrice(Country, Currency)? What if I need to use paymentPrice from my entity to perform some business rule checks?
I'm trying to wrap my head around it and I think I'm overthinking it and it hurts. ;)

I would lean towards your second option of having a PaymentPriceCalculator. This way you would be able to have different calculators if you ever decided to change the algorithm or use multiple algorithms.
I would not put it in the Product aggregate since the price varies by country. It would also make your Product class more complicated. From a domain point of view, aren't 2 otherwise identical products "equal" even if they are purchased in different countries?
Visitor pattern doesn't really fit here.
I might also have a 2nd service that converts $ into whatever currency is needed. This should be separate service since your domain appears to use dollars for everything until the very end when the user needs to actual pay for stuff. This way you can also add applicable taxes / VAT etc as separate from the logic of figuring out price exceptions by country.

Agree with #dkatzel, aggregate is not a good place to hold calculation.
Assume that the product hold the calculation:
product.paymentPrice(countryId, currency, fixedPriceMatrix)
To test this, you need to build a product in each test case although some cases focus on currency choosing only.
countryId = ...
currency = ...
fixedPriceMatrix = ...
basePrice = ...
countryPrice = ...
product = new Product(id, basePrice, countryPrice...)
paymentPrice = product.paymentPrice(countryId, currency, fixedPriceMatrix)
In real projects, aggregates holds many information (for different purposes) which makes them relatively more difficult to setup in tests.
The easies way to test the current calculation is using a value object PaymentPrice
//in unit test
paymentPrice = new PaymentPrice(basePriceForYourCountry, currency, fixedPriceMatrix)
value = paymentPrice.value()
//the product now holds countryPrice calculation
countryPrice = new Product(id, basePrice).countryPrice(countryId);
You can use PaymentPriceCalculator as a stateless domain service as the factory:
class PaymentPriceCalculator {
PaymentPrice paymentPrice(product, countryId, currency) {
fixedPriceMatrix = fixedPriceMatrixStore.get()
return new PaymentPrice(product.countryPrice(countryId), currency, fixedPriceMatrix())
}
}
The potential change could be:
algorithm change(you can extract PaymentPrice as superclass and introduce various subclass for different algorithms)
more options for user. This may break existing method signature to add more parameters. You can introduce a parameter object to hold countryId, currency and others.

I think the most appropriate design pattern for this problem is Strategy Design Pattern.
Please refer below links to understand how it can be applied in this scenario.
Strategy Design Pattern - Payment Strategy
Strategy Design Pattern - Overview
Please note you can combine Strategy Pattern with other patterns like Factory and Composite if you need more than one strategy to get the final price.

I would declare an object responsible for calculating Product's price, for example:
interface ProductPriceCalculator {
public function determineProductPrice($productId, Currency $currency = null);
}
Since each product has the basePrice and we would like to modify it can be organized like this:
class Product {
private $id;
/**
* Initially base price
* #var Money
*/
private $price;
public function modifyPrice(ProductPriceCalculator $calculator, Currency $currency = null) {
$newPrice = $calculator->determineProductPrice($this->id, $currency);
if ($newPrice !== null) {
$this->price = $newPrice;
}
}
}
Now in this case you need the country to be the price modifier. The solution that makes sense to me is to reflect that in code exactly:
class Country implements ProductPriceCalculator {
private $id;
/**
* #var Currency
*/
private $currency;
/**
* Hashmap<String, Money> where product id evaluates to price in $this country
* #var array
*/
private $productPrices = array();
/**
* #param string $productId
* #param Currency $currency
* #return Money
*/
public function determineProductPrice($productId, Currency $currency = null) {
if (array_key_exists($productId, $this->productPrices)) {
$productPrice = clone $this->productPrices[$productId];
if ($currency !== null) {
$currency = $this->currency;
}
return $productPrice->convertTo($currency);
}
}
}
Now to support money and currency logic:
class Money {
private $value;
private $currency;
public function __construct($amount, Currency $currency) {
$this->value = $amount;
$this->currency = $currency;
}
public function convertTo(Currency $newCurrency) {
if (!$this->currency->equalTo($newCurrency)) {
$this->value *= $newCurrency->ratioTo($this->currency);
$this->currency = $newCurrency;
}
}
}
class Currency {
private $code;
private static $conversionTable = array();
public function equalTo(Currency $currency) {
return $this->code == $currency->code;
}
public function ratioTo(Currency $currency) {
return self::$conversionTable[$this->code . '-' . $currency->code];
}
}
And finally the client would look something like this:
class SomeClient {
public function someAction() {
//initialize $product (it has the base price)
//initialize $selectedCountry with prices for this product
//initialize $selectedCurrency
$product->modifyPrice($selectedCountry, $selectedCurrency);
//here $product contains the price for that country
}
}

Related

How to create filters in Spring boot based on a List

I have to created filters data in spring boot. From frontEnd, I am sending a list containing the selected id of each category.I need to return items based on this list. I can do it this way
Service:
public List<ProductModel> getProductFilter(Integer[] categories) {
int size = categories.length;
if(size == 1){
return productRepository.getProductFilteredByOneCategory(Long.valueOf(categories[0]));
}else {
return productRepository.getProductFilteredByTwoCategory(Long.valueOf(categories[0]),Long.valueOf(categories[1]));
}
}
}
Repository:
#Query("SELECT c FROM ProductModel c WHERE c.categoryModel.id = :Category_id")
List<ProductModel> getProductFilteredByOneCategory(Long Category_id);
#Query("SELECT c FROM ProductModel c WHERE c.categoryModel.id = :Category_idOne OR c.categoryModel.id = :Category_idTwo")
List<ProductModel> getProductFilteredByTwoCategory(Long Category_idOne, Long Category_idTwo);
But if there are 50 of these categories, it is useless. Can anyone tell me how to make it better? There is some way to generate a query from a list?
You can use in instead of using multiple or operations as follows. It select all productModels match any categoryModel id in List.
#Query("SELECT c FROM ProductModel c WHERE c.categoryModel.id in category_ids")
List<ProductModel> getProductFilteredByCategoryIds(List<Long> Category_ids);
As #YJR said, IN clause is the solution, but you should also consider declaring query method as shown below, which doesn't require writing JPQL.
public List<ProductModel> findByCategoryModel_IdIn(List<Long> categoryIds);

fold() into a set Koan is not accepted

I am learning Kotlin via Koans here: https://play.kotlinlang.org/koans/Collections/Fold/Task.kt
this is my code so far:
// Return the set of products that were ordered by all customers
fun Shop.getProductsOrderedByAll(): Set<Product> {
val orderedProductsByAll = this.customers.fold(mutableSetOf<Product>()) {
collector, c -> collector.plus(c.getOrderedProducts()).toMutableSet()
}
return orderedProductsByAll
}
fun Customer.getOrderedProducts(): List<Product> =
this.orders.flatMap { o -> o.products }
Unfortunately the results is not accepted:
Fail: testGetProductsOrderedByAllCustomers: The function 'getProductsOrderedByAll' is implemented incorrectly
Any tips on what I am doing wrong here?
You have probably misunderstood the question...
Return the set of products that were ordered by all customers
means
Give me the products that are been purchased by all customer
therefore, you don't want to return "all the products that are been purchased at least 1 time by any customer", but only those that every customer have bought at least one time
At the end, "mathematically speaking" you don't want an "union", but an "intersection"... and the collector has infact an intersect method...
OT note:
also, consider that your plus method is not doing anything to the set, and your code is equivalent to:
return customers.flatMap { c -> c.getOrderedProducts() }.toSet()

LINQ or Navigation Properties command to retrieve 1 to many data

I am looking for help with a LINQ SQL query please.
I have a blazor application that gets data from an Azure SQL database. I am seeking to get a dataset from the database for linking to a datagrid, where each row is a record from the main table joined with a record from the second table. The second table has millions of records, it needs to join one record which has the same key (securityId) and with the date being the record with the nominated date, or with the last date before the nominated date.
Because of the size of the 2nd file, I need an efficient query. Currently I am using the following, but I believe there must be more efficient ways to do it without the lag. Also tried Navigation Properties but couldn't get to work
reviewdateS is the date that I want the 2nd record to match or be the latest date prior to that date
result = (from cmpn in _ctx.MstarCompanies
join prcs in _ctx.MstarPrices
on cmpn.securityId equals prcs.securityId into cs
from c in cs.DefaultIfEmpty()
where c.date01 == reviewDateS
select new ClsMarketPrices { })
Following are the 3 relevant classes. ClsMarketPrices does not relate to a database table, it is simple a class that combines the other 2 classes which may not be necessary but with my limited knowledge it is how it is working.
_ctx is a repository that links to the data context.
public MySQLRepositories(ApplicationDbContext ctx)
{
_ctx = ctx;
}
public class ClsMarket
{
[Key]
public int CompanyId { get; set; } = 0;
public string securityId { get; set; } = "";
public string companyName { get; set; } = "";
public string mic { get; set; } = "";
public string currency { get; set; } = "";
[ForeignKey("securityId")]
public virtual ICollection<ClsPrices> Prices { get; set; }
}
public class ClsMarketPrices
{
[Key]
public int CompanyId { get; set; } = 0;
public string companyName { get; set; } = "";
public string period { get; set; } = "";
public string mic { get; set; } = "";
}
public class ClsPrices
{
[Key]
public int PricesId { get; set; }
[ForeignKey("securityId")]
public string securityId { get; set; } = "";
public string mic { get; set; } = "";
public string date01 { get; set; } = "";
public virtual ClsMarket ClsMarket {get; set;}
}
I want to get a record from the 1st file joined with a record from the 2nd file where that record from the 2nd file has a date equal to or the last before the nominated date.
So we are talking about files, not a database! This is important, because this means that your local process will execute the LINQ, not a database management system. In other words: the LINQ will be IEnumerable, not IQueryable.
This is important, because as Enumerable, you will be able to define your own LINQ extension methods.
Although you supplied an enormous amount of irrelevant properties, you forgot to give us the most important things: you were talking about two files, you told us that you have two classes with a one-to-many relation, but you gave us three classes. Which ones do have the relation that you are talking about?
I think that every object of ClsMarketPrices has zero or more ClsPrices, and that every ClsPrice is one of the prices of a ClsMarketPrices, namely the ClsMarketPrices that the foreign key SecurityId (rather confusing name) refers to.
First of all, let's assume you already have procedures to read the two sequences from your files. And of course, these procedures won't read more than needed (so don't read the whole file if you will only use the first ClsMarket). I assume you already know how to do that:
IEnumerable<ClsMarketPrices> ReadMarketPrices();
IEnumerable<ClsPrices> ReadPrices();
So you've go a DateTime reviewDate. Every MarketPrice has zero or more Prices. Every Price has a DateTime property DateStamp. You want for every MarketPrice the Price that has the largest value for DateStamp that is smaller or equal to reviewDate.
If a MarketPrice doesn't have such a Prices, for instance because it doesn't have a Price at all, or all its Prices have a DateStamp larger than reviewDate, you want a value null.
You didn't say what you want if a MarketPrice has several Prices with equal largest DateStamp <= reviewDate. I assume that you don't care which one is selected.
The straighforward LINQ method would be to use GroupJoin, Where, Orderby and FirstOrDefault:
DateTime reviewDate = ...
IEnumerable<ClsMarketPrices> marketPricess = ReadMarketPrices();
IEnumerable<ClsPrices> prices = ReadPrices().Where(price => price.DateStamp <= reviewDate);
// GroupJoin marketPrices with prices:
var result = markets.GroupJoin(prices,
marketPrice => marketPrice.CompanyId, // from every MarketPrice take the primary key
price => price.CompanyId, // from every price take the foreign key to its market
// parameter resultSelector: from every market, with its zero or more matching prices
// make one new:
(marketPrice, pricesOfThisMarketPrice) => new
{
// select the marketPrice properties that you plan to use:
Id = marketPrice.CompanyId,
Name = ...
...
// from all prices of this marketPrice, take the one with the largest DateStamp
// we know there are no marketPrices with a DataStamp larger than reviewData
LatestPrice = pricesOfThisMarketPrice.OrderbyDescending(price => price.DateStamp)
.Select(price => new
{
// Select the price properties you plan to use;
Id = price.PricesId,
Date = price.DateStamp,
...
})
.FirstOrDefault(),
});
The problem is: this must be done efficiently, because you have an immense amount of Markets and MarketPrices.
Althoug we already limited the amount of prices to sort by removing the prices that are after reviewDate, it is still a waste to order all Dates if you will only be using the first one.
We can optimize this, by using Aggregate for pricesOfThisMarketPrice. This will assert that pricesOfThisMarketPrice will be enumerated only once.
Side remarks: Aggregate only works on IEnumerable, not on IQueryable, so it won't work on a database. Furthermore, pricesOfThisMarketPrice might be an empty sequence; we have to take care of that.
LatestPrice = pricesOfThisMarketPrice.Any() ?
pricesOfThisMarketPrice.Aggregate(
// select the one with the largest value of DateStamp:
(latestPrice, nextPrice) => nextPrice.DateStamp >= latesPrice.DateStamp) ? nextPrice : latestPrice)
// do not do the aggregate if there are no prices at all:
: null,
Although this Aggregate is more efficient than OrderBy, your second sequence will still be enumerated more than once. See the source code of Enumerable.GroupJoin.
If you really want to enumerate your second source once, and limit the number of enumerations of the first source, consider to create an extension method. This way you can use it as any LINQ method. If you are not familiar with extension methods, see extension methods demystified.
You can create an extension method for your ClsPrices and ClsPrice, however, if you think you will need to "find the largest element that belongs to another element" more often, why not create a generic method, just like LINQ does.
Below I create the most extensive extension method, one with a resultSelector and equalityComparers. If you will use standard equality, consider to add an extension method without these comparers and let this extension method call the other extension method with null value for the comparers.
For examples about the overloads with and without equality comparers see several LINQ methods, like ToDictionary: there is a method without a comparer and one with a comparer. This first one calls the second one with null value for comparer.
I will use baby steps, so you can understand what happens.
This can slightly be optimized.
The most important thing is that you will enumerate your largest collection only once.
IEnumerable<TResult> TakeLargestItem<T1, T2, TKey, Tproperty, TResult>(
this IEnumerable<T1> t1Sequence,
IEnumerable<T2> t2Sequence,
// Select primary and foreign key:
Func<T1, TKey> t1KeySelector,
Func<T2, TKey> t2KeySelector,
// Select the property of T2 of which you want the largest element
Func<T2, TProperty> propertySelector,
// The largest element must be <= propertyLimit:
TProperty propertyLimit,
// From T1 and the largest T2 create one TResult
Func<T1, T2, TResult> resultSelector,
// equality comparer to compare equality of primary and foreign key
IEqualityComparer<TKey> keyComparer,
// comparer to find the largest property value
IComparer<TProperty> propertyComparer)
{
// TODO: invent a property method name
// TODO: decide what to do if null input
// if no comparers provided, use the default comparers:
if (keyComparer == null) keyComparer = EqualityComparer<TKey>.Default;
if (propertyComparer == null) propertyComparer = Comparer<TProperty>.Default;
// TODO: implement
}
The implementation is straightforward:
put all T1 in a dictionary t1Key as key, {T1, T2} as value, keyComparer as comparer
then enumerate T2 only once.
check if the property <= propertyLimit,
if so, search in the dictionary for the {T1, T2} combination with the same key
check if the current t2Item is larger than the T2 in the {T1, T2} combination
if so: replace
We need an internal class:
class DictionaryValue
{
public T1 T1 {get; set;}
public T2 T2 {get; set;}
}
The code:
IDictionary<TKey, DictionaryValue> t1Dict = t1Sequence.ToDictionary(
t1 -> t1KeySelector(t1),
t1 => new DictionaryValue {T1 = t1, T2 = (T2)null },
keyComparer);
The enumeration of t2Sequence:
foreach (T2 t2 in t2Sequence)
{
// check if the property is <= propertyLimit
TProperty property = propertySelector(t2);
if (propertyComparer.Compare(property, propertyLimit) < 0)
{
// find the T1 that belongs to this T2:
TKey key = keySelector(t2);
if (t1Dict.TryGetValue(key, out DictionaryValue largestValue))
{
// there is a DictionaryValue with the same key
// is it null? then t2 is the largest
// if not null: get the property of the largest value and use the
// propertyComparer to see which one of them is the largest
if (largestValue.T2 == null)
{
largestValue.T2 = t2;
}
else
{
TProperty largestProperty = propertySelector(largestValue.T2);
if (propertyComparer.Compare(property, largestProperty) > 0)
{
// t2 has a larger property than the largestValue: replace
largestValue.T2 = t2,
}
}
}
}
}
So for every t1, we have found the largest t2 that has a property <= propertyLimit.
Use the resultSelector to create the results.
IEnumerable<TResult> result = t1Dict.Values.Select(
t1WithLargestT2 => resultSelector(t1WithLargestT2.T1, t1WithLargestT2.T2));
return result;

CRM 2013 Updating the Quote Product section

Has anyone tried to create a plugin that updates the record's values in the Quote Product form? I created one, because I need a custom formula that calculates the Extended Amount field, but there are automatic calculations in the CRM that fill these fields. This doesn't allow me to update the formula of calculation at all.
What my plugin do, is:
Gets the values from the fields Price per unit, Quantity and Discount % (which is a custom field);
Calculates the value that I need;
Sets it at the extended amount field.
But, none of this works because I get a "Business Process Error";
Basically this error tells me that I can't use the record's guid to access it.
Here is my code:
protected void ExecutePostQuoteProductUpdate(LocalPluginContext localContext)
{
if (localContext == null)
{
throw new ArgumentNullException("localContext");
}
IPluginExecutionContext context = localContext.PluginExecutionContext;
IOrganizationService service = localContext.OrganizationService;
Guid quoteProductID = (Guid)((Entity)context.InputParameters["Target"]).Id;
ColumnSet set = new ColumnSet();
set.AllColumns = true;
var quote = service.Retrieve("quotedetail", quoteProductID, set);
var priceperunit = quote.Attributes["priceperunit"];
var teamleader = quote.Attributes["new_disc"];
var manualdiscountamount = quote.Attributes["manualdiscountamount"];
var volumediscountamount = quote.Attributes["volumediscountamount"];
var VAT = (int)quote.Attributes["new_vat"];
var discountamount = (double)priceperunit * (double)teamleader / 100;
var baseamount = (double)priceperunit - discountamount;
var tax = baseamount * VAT / 100;
var extendedamount = baseamount + tax;
quote.Attributes["new_discountamount"] = discountamount;
quote.Attributes["baseamount"] = baseamount;
quote.Attributes["tax"] = tax;
quote["description"] = priceperunit;
quote.Attributes["extendedamount"] = extendedamount;
service.Update(quote);
}
Please, tell me if there is a way to access those fields and use/set them.
Thanks in Advance!
:/
You cannot update extendedamount directly - instead if you use the built-in fields which automatically calculate it then CRM will take care of this for you.
Those fields are: baseamount, quantity, discountamount, and tax. These are Money fields so you will need to store them as such:
quote.Attributes["baseamount"] = new Money(baseamount); // Money and decimal numbers use the "decimal" datatype and not double.
As for your custom calculation, you just need to convert the percentage stored in your custom field into the actual amount and assign it to the discountamount attribute. Something like:
var discountamount = (decimal)priceperunit * (decimal)teamleader / 100;
// Assigning the actual discount amount will automatically recalculate the extended amount on the line.
// No need to recalculate all fields.
quote.Attributes["discountamount"] = new Money(discountamount);
Note that tax may need to be recalculated to reflect the discount, but the process should work exactly the same.

Propel ORM - how do I specify tables to reverse engineer?

I have a database with a few dozen tables. My app deals with just one of those tables. I'd like propel to generate the schema just for that table. Is there a way I can specify the table(s) in build.properties?
A simple extension of Propel (code examples based on Propel-1.6.8) to allow ignore a custom set of tables (configured using a property):
Extend build-propel.xml:
<!-- Ignore Tables Feature -->
<taskdef
name="propel-schema-reverse-ignore"
classname="task.PropelSchemaReverseIgnoreTask" classpathRef="propelclasses"/>
Extend build.xml:
<target name="reverse-ignore" depends="configure">
<phing phingfile="build-propel.xml" target="reverse-ignore"/>
</target>
Extend PropelSchemaReverseTask:
class PropelSchemaReverseIgnoreTask extends PropelSchemaReverseTask {
/**
* Builds the model classes from the database schema.
* #return Database The built-out Database (with all tables, etc.)
*/
protected function buildModel()
{
// ...
// Loads Classname reverse.customParserClass if present
$customParserClassname = $config->getBuildProperty("reverseCustomParserClass");
if ($customParserClassname!=null) {
$this->log('Using custom parser class: '.$customParserClassname);
$parser = $config->getConfiguredSchemaParserForClassname($con, $customParserClassname);
} else {
$parser = $config->getConfiguredSchemaParser($con);
}
// ...
}
}
Add function to GeneratorConfig:
class GeneratorConfig implements GeneratorConfigInterface {
// ...
/**
* Ignore Tables Feature:
* Load a specific SchemaParser class
*
* #param PDO $con
* #param string $clazzName SchemaParser class to load
* #throws BuildException
* #return Ambigous <SchemaParser, unknown>
*/
public function getConfiguredSchemaParserForClassname(PDO $con = null, $clazzName)
{
$parser = new $clazzName();
if (!$parser instanceof SchemaParser) {
throw new BuildException("Specified platform class ($clazz) does implement SchemaParser interface.", $this->getLocation());
}
$parser->setConnection($con);
$parser->setMigrationTable($this->getBuildProperty('migrationTable'));
$parser->setGeneratorConfig($this);
return $parser;
}
}
Extend MysqlSchemaParser:
class MysqlSchemaIgnoreParser extends MysqlSchemaParser {
public function parse(Database $database, Task $task = null)
{
$this->addVendorInfo = $this->getGeneratorConfig()->getBuildProperty('addVendorInfo');
$stmt = $this->dbh->query("SHOW FULL TABLES");
// First load the tables (important that this happen before filling out details of tables)
$tables = array();
$configIgnoreTables = $this->getGeneratorConfig()->getBuildProperty("reverseIgnoreTables");
$tables_ignore_list = array();
$tables_ignore_list = explode(",", $configIgnoreTables);
if ($task) {
$task->log("Reverse Engineering Tables", Project::MSG_VERBOSE);
}
while ($row = $stmt->fetch(PDO::FETCH_NUM)) {
$name = $row[0];
$type = $row[1];
if (!in_array($name, $tables_ignore_list)) {
// ...
} else {
$task->log("Ignoring table: " .$name);
}
}
// ...
}
}
Add new (optional) properties to build.properties:
# Propel Reverse Custom Properties
propel.reverse.customParserClass=MysqlSchemaIgnoreParser
propel.reverse.ignoreTables = table1,table2
A solution could be to write your own schema parser that ignores certain tables. These tables to exclude could be read from a config file or so.
Take a look at the MysqlSchemaParser.php to get an idea of how such a parser works. You could extend such an existing parser and just override the parse() method. When tables are added, you would check them for exclusion/inclusion and only add them if they meet your subset criteria.
How to create custom propel tasks is described in the Propel cookbook.
Instead of invoking propel-gen reverse you would then invoke your customized reverse engineering task.
If done neatly, I'd find it worth being added as a contribution to the Propel project and you'd definitely deserve fame for that! :-)