Vulcan fires up its language and commerce engines
It’s been over a week since I first launched the alpha of Vulcan, the lightweight Elasticsearch client for Episerver. Since then I’ve been working hard to test it, stretch it and improve it. Some of it has simply ben bug fixes and simplifications but most of the effort has gone into handling analysis of textual content. The issue is that we want to analyse ‘free text’ content as language, but things like product codes shouldn’t be analyzed at all. Fortunately, Elasticsearch has a great feature called Multi-Fields. This enables us to deal with the field as non-analyzed, but also analyze and store a copy of those fields so that we can do free-text queries against it. So what has changed generally in Vulcan, and how do you use the new language handling?
Before we start, just one important note. I recommend you use an Elasticsearch 2.x cluster. I found that the 1.x clusters I was testing on didn’t work so nicely with the latest version of NEST, which kind of expects a 2.x cluster. I did get the Vulcan core running fine against a 1.x index, but I can’t guarantee that your queries will work as expected. You may get some random 400 bad requests as the NEST client creates 2.x compatible queries and tries to pass them to the 1.x index. For that reason, if you are testing then I suggest you use a 2.x cluster. I found a free one you can use in the cloud from Bonsai, or you can of course host your own.
Other than that, the most significant change is something that you won’t see at first glance. I’ve split the index across multiple language-based indexes. This is Elasticsearch recommended best practice, so it seemed the right thing to do. When you now run the Vulcan Index Content scheduled job, you’ll see an index created per language, with the start of the index the name you set in the web.config. So, for example, if you set a Vulcan index name of ‘vulcan’, then you might see indexes called ‘vulcan_en’, ‘vulcan_de’, ‘vulcan_invariant’ and so forth. That last one – the invariant index – is particularly interesting as it’s where all the content is stored that is not localizable. You can get a handle to it by getting a Vulcan client for CultureInfo.InvariantCulture:
var client = VulcanHandler.Service.GetClient(CultureInfo.InvariantCulture);
model.ContentHits = VulcanHandler.Service.GetClient().SearchContent<IContent>(d => d
.Query(query => query.SimpleQueryString(sq => sq.Fields(fields => fields.Field("*.analyzed")).Query(q)))
.Highlight(h => h.Encoder("html").Fields(f => f.Field("*")))
.Aggregations(agg => agg.Terms("types", t => t.Field("_type"))));
public class PriceConstruct
{
public string MarketId { get; set; }
public Money Price { get; set; }
}
public IEnumerable<PriceConstruct> Price
{
get
{
var prices = new List<PriceConstruct>();
foreach (var market in ServiceLocator.Current.GetInstance<IMarketService>().GetAllMarkets())
{
var variantPrices = this.GetPrices(market.MarketId, Mediachase.Commerce.Pricing.CustomerPricing.AllCustomers);
if (variantPrices != null)
{
foreach (var price in variantPrices)
{
if (price.MinQuantity == 0 && price.CustomerPricing == Mediachase.Commerce.Pricing.CustomerPricing.AllCustomers) // this is a default price
{
prices.Add(new PriceConstruct() { MarketId = market.MarketId.Value, Price = price.UnitPrice });
break;
}
}
}
}
return prices;
}
}
All this does is loops the prices and tries to get the default prices for the various markets. You could of course make this more robust like checking currency, but this is just a simple example. Now that we have this property, when we run our index job it will get persisted into Elasticsearch. We can now query it with Vulcan something like this (this is from my Quicksilver demo that I’ve updated to use Vulcan):
model.SearchResponse = VulcanHandler.Service.GetClient().SearchContent<EPiServer.Reference.Commerce.Site.Features.Product.Models.FashionVariant>(
q => q.Aggregations(a => a
.Filter("current_market", cm => cm
.Filter(f => f
.Term(p => p
.Price.First().MarketId, CurrentMarket.Service.GetCurrentMarket().MarketId.Value))
.Aggregations(agg => agg
.Terms("prices", t => t
.Field(fld => fld.Price.First().Price.Amount))))));
<add key="VulcanUrl" value="https://vulcancluster-452277433331.eu-west-1.bonsai.io/" />
<add key="VulcanUsername" value="jkda99asdk" />
<add key="VulcanPassword" value="r9088fsaff" />
<add key="VulcanIndex" value="vulcan_quicksilverdemo" />
DISCLAIMER: This project is in no way connected with or endorsed by Episerver. It is being created under the auspices of a South African company and is entirely separate to what I do as an Episerver employee.
Must say that this was exactly what I was looking for! Keep up the good work!