Coffee roasters in Stockholm – a review

If you are serious about Espresso quality, you know you must by from a specialty roaster – not from the super market. You will pay more premium price, likely 300kr+ per kg, up to 500kr per kilo, compare to around 100-150kr per kg from super market. In return, you get:

  • Much better freshness. Most, if not all coffees from super market only have “expired date”, not “roast date”. You can probably guess the roast date by subtracting expired date by 24 months and the most fresh one I could find, was two months old. At this point the coffee already started degrading in quality. In contrast, when I buy from roaster, it is always less than 1 week from roast date, which is clearly printed on the bag. As a rule of thumb, you should finish your coffee in less than 8 weeks from roast date.
  • Much better roasted. Most coffee from super market is roasted with super hot air (800*c) in very short amount of time. This allows the roaster to roast ton after ton, but with the cost of coffee flavor. Specialty coffees are often roasted in much smaller batches, in
  • Traceability. You only know coffee from super market by their country origin, and that’s it. But for specialty coffee, you will know the region which produced the coffee, and in many cases, even the farm that produced it.
  • Last but not least, support for local businesses. Specialty roasters are small businesses in your city, or even area. Buying from them means you support your local economy.

What to look for from Coffee roasters

A coffee bean bag with one way valve is a must. Best if it is resealable.

StockholmRoast

They offers good prices, but no subscription. They ship through DHL to the service point, with free shipping, which is nice, but not the best.

The bags are well packaged, so you get proper protection of your precious coffee beans. But they also glue a plastic bag on the package (for the shipping information and the receipt), which is quite tiresome to remove for proper recycling.

The ink on the bag could easily get into you hands, especially if they are a little wet. They are, however, not very easy to wash off.

Lykke

Lykke offers subscriptions, with 10% discount, which is good. Importantly, they ship directly to your mailbox. Order, and in one day or two, you find your favorite coffee

You can easily manage subscriptions, including changing it, skip one delivery, or cancel it, which is a huge plus.

Kafferosterietkoppar

Sage/Breville Barista Pro review

This is a super short review of this fairly popular espresso machine. I bought this last year, despite a lot of arguments from my wife. She even threatened to throw it out if I bought. I did. And now she demands latte/cappuccino every day!

My budget was pretty limited at that point, so other decent options (HX or even dual boiler machines) are out of reach. Barista Pro fits in my budget (and kitchen), and when Amazon had a very good discount on them, I pulled the trigger.

I was happy.

When it was new

Pros:

Sage/Breville is feature oriented, and when you open the box, you have everything you need to get going: a milk jug, a 54mm portafilter with 4 different baskets (2 double shots (1 pressurized, 1 non pressurized), 2 single shot), and of course, a grinder built-in. If you are new, this is hugely important. Some sellers do not include the milk jug, or even tamper (looking at you, Lelit!), and it’s bad that you are excited to open your new fancy espresso machine and realize you can’t make a decent cappuccino due to lacking equipment. The UI is intuitive and easy to work with. Once you understand the basics, using the machine, UX wise, is simple and easy.

3s start up time. You press a button, and the machine is ready. Truth is, however, if you want to get better shots, you need to flush 1 or 2 cup first. With the empty portafilter inserted, press the double shots button, and let the hot water flows through it. It warms up the head, the portafilter, and make sure you get stabilized temperature in the boiler.

The flow is well defined, and smooth – you take the portafilter, put it in the holder and click – the grinder grinds coffee for you, in the fineness you chose and the time you pick. Then you take the tamper (attached to the machine using a magnet, a pretty smart design), tamp it, put it in the head, place your cup, and press a button. It is the convenience you are paying for.

Cons:

One you open the box, you quickly realize this machine is not built to last. It’s a thin layer of stainless steel outside of plastic. Build quality is … fine, but don’t expect the same quality as Italy-made machine. It’s been reported that while Sage/Breville service is very good during warranty, but one you are out of warranty, you have to pay hefty fee for repairs, because they just break down. And repair usually means “replace”.

Another downside is that this uses a 54mm portafilter. The portafilter itself is fine, well made and solid, but after a while, you will want to try out new things, like bottomless portafilter. But this is when you realize you are left with either options: 1. buy cheap no brand products from China or 2. absurdly expensive or 3. both. Should it come with a 58mm portafilter which is the “industry standard”, you will have more options from reputable brands, at reasonable prices.

The built-in grinder is merely adequate, it’s step conical burr grinder. You will be able to grind espresso with it, but not with the fineness adjustment needed to extract the best out of your coffee. Whenever you can, upgrade to a good espresso grinder would make a huge difference in your espresso. (Note: a good espresso grinder can easily cost $400 or so!). Also, cleaning it is not the easiest task – it’s doable, but requires additional tools (like a vacuum cleaner) to do it properly.

The steam wand is ok, but it is on the weak side, and it produces wetter steam than I would like. To make great foamy, silky milk, you need powerful dry steam.

It’s messy to grind a double shot (18-20gr), because some coffee ground will be left on the portafilter holder, or on the drip tray. Yes you can use a dosing cup, or a funnel, but you will, once again, agonize the limited options of a 54mm portafilter.

In the end, Barista Pro is a well rounded, full featured espresso machine. It’s a budget/entry one, capable of making good shots. But it does not really excel in neither brewing, nor steaming. Once you horned your skill, upgrading to a better grinder, and a better 58mm portafilter will be a big step.

If you want to learn and can spend, of course.

HP inkjet printer is a scam

I occasionally need to print something, and based on the reviews in rtings.com, a HP OfficeJet Pro 9013 was probably my best bet for performance/price (given the top choice is not available anywhere in my region – Europe/Sweden). Researching showed some shady business practice by HP for their inkjet printers, but against my best judgement, I decided to try my luck.

My mistake.

The printer worked fine for a while. Nothing particularly good or bad about it. It prints, it scans, it fits what I need. I bought it new at a reasonable price, and got 3 years commercial warranty which seems like a good deal.

But the honeymoon does not last long.

The yellow cartridge went out first. It does not let me print even black and white document.

Why do I need yellow when I only want to print B&W?

This is crappy business, and I was warned, but OK fine. I signed on for this. So I bough the yellow cartridge to replace the old one. All good? No, this time, it’s cyan that is out of ink.

You can see Magenta is still there

Even without printing one page.

I am forced to buy cyan. Just to print one page, in black and white.

And then, when cyan cartridge arrives, I replaced the old one, and guess what, magenta is out!

Until it is magically out

Even without printing one page.

I can’t help but to heavily suspect that HP does that intentionally.

The printer fails to do its job when you need it, sometime sorely. This entirely defeats the convenience of having your own printer at home. I could have just printed it by a service, and be done with it.

I called HP support and told that “the cartridges should be replaced at the same time”, and “just get a new cartridge and it will work”. You can see, that is less than helpful.

This is simply a terrible, terrible practice from HP (and sadly it’s not uncommon in the business)

I learned my lesson – never again.

Announcing Pro Optimizely Commerce Cloud

Yes, you guess it right, it’s a(nother) book

It has been 5 years since I started Pro Episerver Commerce back in early 2016. The book was a success, not as big as I hoped for, but definitely bigger than I expected. Tackling a niche market, it was fairly popular within the community, and it gave me a lot of happiness (and some pocket changes) to see that it helped many developers to understand and use the framework – which I help created, and love – better.

So much has changed in the last 5 years.

I have my first kid, and a second one. I left Commerce development team, to work on my own, then have a small team. Episerver bought Optimizely, then rebrand.

And so much more has happened with Episerver Commerce, more than just being renamed to Optimizely Commerce Cloud.

It deserves a new book!

To celebrate my 10th anniversary with Episerver (now Optimizely), I am proud, and excited to announce the second edition of Pro Episerver Commerce – Pro Optimizely Commerce Cloud. Most of the content written in Pro Episerver Commerce is still very much applicable, but I feel there is a need to refocus and expand on important parts.

You can register your interests today at https://leanpub.com/prooptimizelycommercecloud

Purchasers of Pro Episerver Commerce – even if you obtained the free version – will receive a 40% discount code for the new book – so don’t miss it.

I will see you there!

Lessons learned about exception handling and logging

Exception handling and logging is essential part of any site. Your site will eventually run into problems, and as a developer it’s your job to make sure that you have enough – or at least helpful – information to look into problems. From my years of diagnosing and root cause analysis, these are the most common problems about exception handling and logging. You (or any developers that come after you) can thank yourself later.

Empty try catch is almost always bad

In my early days of professional programming, I saw a colleague – several years senior to me, fortunately, not at Episerver – write a lot of this code, basically in each and every method:

try { 

//do stuffs

}

catch {}

You surely don’t want to show a YSOD screen to your visitors, but keep this in mind – this is almost always bad. You are trying to hide errors, and even worse, trying to swallow it. This should be a red flag for any code review.

There are cases when you truly, really want to swallow an exception. It’s rare, but it’s a reality. In such case, make sure to explain why you have an empty catch in comments.

Don’t catch exception only to throw it

Does this look familiar to you, if yes, then your code base have a problem (or actually, two)

try 
{
//do stuffs
}
catch {Exception ex}
{
throw ex;
}

But why?

  • It is wasting a try catch doing nothing of value. Log in, wrap it in a different type of exception (with probably more information, not just rethrow the exception
  • throw ex; actually reset the stacktrace. If you simply want to rethrow the exception (after doing meaningful stuffs with it), use throw; instead. That will preserve the precious stacktrace for exception handling at higher level.

Logging only the message is a crime

Someday, you will find an entry in your log looks like this

Object reference not set to an instance of an object.

And that’s it. This is even worse than no message – you know something is wrong, but you don’t know why, or how to fix it. It’s important to always log the full stacktrace of the exception, instead of just the message, unless you have a very good reason (I can’t think of one, can you?) not to.

Verbose is better than concise

This is an extension of the above lesson. You are already logging the entire stacktrace, but is your exception message helpful? One example is this

[InvalidOperationException: There is already a content type registered with name: PdfFile]
   EPiServer.DataAbstraction.Internal.DefaultContentTypeRepository.ValidateUniqueness(ContentType contentType) +222
   

Which other type was registered with same name? Is it a built-in (i.e. system) type, or a type from a 3rd party library, or a type that you added but forgot yourself?

In this case, a much better exception message would be:

There are two or more content types registered with name PdfFile

Assembly1.Namespace1.PdfFile
Assembly2.Namespace2.PdfFile

Exception message is not only showing an error/unwanted situation has happened, but it needs to be helpful as well. When you log a message, you should try to add as much information as you can so the ones who will be looking into the issue can make a good guess of what is wrong, and how to fix it. There is virtually no limit on how verbose you can be, so feel free to add as much information as you need (I’m not asking you to add the entire “War and Peace” novel here, of course). You can thank me later.

Commerce relation(ship), a story

There are two big types of relations in Episerver (Optimizely) B2C Commerce: relations between entries and nodes, and between nodes. As you will most likely have to touch one or another, or both, sooner or later, this post aims to give you some understanding on how they are structured/work.

Node-Entry relation

When you add a product (or variant, or package, or bundle) to a category, you are creating a NodeEntryRelation. And there are a two types of NodeEntryRelation

  • Primary NodeEntryRelation, which means the category is counted as true parent of the entry. Each entry can only have at most one primary NodeEntryRelation (Which means it can have no primary NodeEntryRelation at all).
  • Secondary NodeEntryRelation, which means the entry is linked to the category. You do that when it makes sense for the product to be in additional categories. for example, a GoPro can be in Camera (primary category), but can also be in Sport Gears (linked). An entry can have no, or multiple secondary NodeEntryRelation.

The concept of primary NodeEntryRelation was added to Commerce 11. Before that, it’s a bit more of a guess work – the “main” category is determined by the sort order – the relation with lowest sort order is considered “main relation”. That introduces some inconsistency, which prompted the rework on that.

What is the main different between those two things? For one thing, access rights. For Commerce, you can set access rights down to categories, but not entries. The entries will inherit access rights from their true parents (i.e. primary nodes). An entry without primary node-entry relation is considered the direct children of a catalog, and inherits its access right settings.

Another smaller difference is that if you delete a category, true children entries will be deleted. Linked entries will only be detached from that category.

NodeEntryRelation can be managed fully by IRelationRepository, using the NodeEntryRelation type, and you can use a few extension methods to make it easier – for example EntryContentBase.GetCategories().

How are your actions in Catalog UI reflected on a data level:

  • When you create a new entry (product/SKU/etc.) in a category, you create a primary node-entry relation for them
  • When you move (cut then paste) an entry to a new category, you are creating a new primary node-entry relation. If the entry already has a primary node-entry relation, the new one will take over.
  • When you link/detach an entry to/from a new category, you are creating/removing a non-primary node-entry relation

Node-Node Relation

Like Node-Entry relation, a node can be a true parent of a node, or just be a “linked” parent.

Unlike Node-Entry relation, Node-Node relation is quite different that it’s separated in two places.

  • Linked nodes are represented by NodeRelation(s) (it might be interesting to know that NodeRelation is the parent class of NodeEntryRelation. The interpretation is that a NodeRelation is – a relation of a node, which can be with another node, or an entry)
  • There is no primary NodeRelation, the true parent node is identified by a property of the category itself. When you have a NodeContent content, then the ParentLink property points to the true parent.

For that reason, a node will always have a true parent, either a catalog, or a node. You can’t use IRelationRepository (and therefore, ServiceAPI) to manage (delete or update) a true parent of a node , you would have to:

  • Set its ParentLink to something else
  • Use IContentRepository.Move to move it to a new parent.

Note that this is the limitation of the Relations API in ServiceAPI. You can technically change the ParentLink of a node and update via POST. It’s just more work and not as intuitive as the Relations API.

Why the disparity, you might ask? Well, a lot of design decisions in Commerce comes from historical reasons, and after that, constrained resources (time/man power) and priority. While the disparity is probably not the best thing you want, it still works fairly well, and if you understand the quirk then it is all well.

Get contact by email address

If you are using Episerver Commerce (or should I say, Optimizely B2C Commerce), you will, at some point, need to get the contact by an email address. That sounds like an easy enough task, until you realize that the class to manage customer contacts CustomerContext has no such method. You will need to find another way, and this is one way you can do it

CustomerContact contact = customerContext.GetContacts().Where(m => m.Email == email)?.FirstOrDefault();

Of course this is not the optimal – avoid it if you can. First of all it loads a lot of contact just to find one. Also while it looks like you are getting all contacts (which is of course something to avoid), you are only get the first 1000 contacts by default, so the code above would return inaccurate result.

Is there a better way?

Yes of course.

Contact was built on “Business Foundation” – think of it as an ORM with extensions. Business Foundation allows great flexibility, with a few caveats. This is how you can find contact by email address:

            try
            {
                var filterEl = new FilterElement("Email", FilterElementType.Equal, email);
                return
                    BusinessManager.List(ContactEntity.ClassName, new[] { filterEl })
                        .OfType<CustomerContact>()
                        .FirstOrDefault();
            }
            catch (ObjectNotFoundException)
            {
                //Safe guard
                return null;
            }

BusinessManager does not have a Get method, so we are use List instead. Note that Email is supposed to be unique, so this should not have any down side with performance (see note below).

This is not limited to email, or to contact. You can find contacts by other properties, or get an Organization with same technique.

It is very important to note, however, this need a proper index on Contact tables, to make sure you are not killing your database.

The book scam on Amazon.se

If you go to Amazon.se and look for Monster Hunter Rise, which is the hottest Nintendo Switch game at the moment, here is the first three results you get:

Could you spot the problem in this picture?

The third item, even if it looks almost exactly the same as the two first ones, is actually a book. Not the Nintendo Switch game as an unsuspecting buyer might think. They go as far as making it looks exactly like a Switch game box with the border and everything.

But that’s that, the buyer put very little effort into this, the description is even a quick copy-paste with a clear error (it’s from Godfall, a different game).

Even if we give the seller the benefits of the doubt (can we really?), this book has several serious issue with it:

  • The cover is a copyright infringement from Nintendo. (To that extend, Nintendo might take this down faster than the fraud-fighting department at Amazon!)
  • The book is 44 pages. A pocket book with 44 pages charging for more than $50. If it’s not a complete ripoff, I don’t know what is.

The type of this scam is as following:

  • Create a book cover that looks exactly like a hot, newly released game box.
  • Fill the book with whatever content
  • Price the book so it’s a compelling buy – lower than the actual game, but not too low (increased margin/lower chance of suspicion)
  • Profit?

What makes it sadder is that this book is shipped and sold by Amazon – they are enabling this type of scam to happen. And this is not the only book that trying to scam buyers. There are several ones that did the same, but removed.

Beware shopping for game out there! Shame on you, scammer, and shame on you Amazon, for doing so little to prevent this type of scam to happen.

Edit:

After I published this post, a new book, this time with “Deluxe edition”, appared:

Amazon, do the minimal due diligent to the products you sell!

Don’t share HttpContext between threads

HttpContext is not designed to be thread-safe, which means you can run into nasty things if you share a HttpContext between thread. For example, this is one of the things that can happen: One (or more) thread is stuck in this:

mscorlib_ni!System.Collections.Generic.Dictionary`2[[System.__Canon, mscorlib],[System.__Canon, mscorlib]].FindEntry(System.__Canon)+ed
EPiServer.Web.Routing.Segments.Internal.RequestSegmentContext.GetOrResolveContextMode(System.Web.HttpContextBase)+9b
EPiServer.DataAbstraction.Internal.ProjectResolver.IsInEditMode()+17
EPiServer.DataAbstraction.Internal.ProjectResolver.GetCurrentProjects()+17
EPiServer.Core.Internal.ProjectPipeline.Pipe(EPiServer.Core.ContentReference, System.Globalization.CultureInfo, EPiServer.Core.LoaderOptions)+1c
EPiServer.Core.Internal.ProviderPipelineImplementation.GetItem(EPiServer.Core.ContentProvider, EPiServer.Core.ContentReference, EPiServer.Core.LoaderOptions)+109
EPiServer.Core.Internal.DefaultContentLoader.TryGet[[System.__Canon, mscorlib]](EPiServer.Core.ContentReference, EPiServer.Core.LoaderOptions, System.__Canon ByRef)+11e
[[StubHelperFrame]]
EPiServer.Core.Internal.DefaultContentLoader.Get[[System.__Canon, mscorlib]](EPiServer.Core.ContentReference, EPiServer.Core.LoaderOptions)+63

If you don’t know why this is scary, this is any infinite loop, meaning your CPU will be spent 100% in to this Dictionary.FindEntry, unable to do anything else. The only way to solve this problem is to restart the instance.

That is caused by unsafe accessing of a Dictionary – if you have a thread that is enumerating it, and another thread trying to write to it, it is possible to run into a dead end like this.

And HttpContext just happens to have many Dictionary properties and sub-properties. HttpContext.Request.RequestContext.RouteData.DataTokens is one of them (And a reason the code above ended in a disaster), making it vulnerable for this kind of problem. Which is exactly why it is not recommended to share a HttpContext between threads.

Sometimes, you can just set HttpContext.Current to null. Sometimes, you need to take a step back and ask yourself that do you really need to run things in parallel?

Get exported Personalization catalog feeds

There are cases that you want to get your Personalization catalog feeds in zip format, maybe to make sure the customizations you have done are there (and are exactly what you want to), or you need to send them to developer support service for further assistance (like why your catalog feeds are not properly imported). Theoretically you can log in as an admin, and go to https://<yoursite>/episerverapi/downloadcatalogfeed to download the feed. But there are few problems with that.

First of all you might have more than one catalog, and the link above only allows you to download the latest one. Secondly it might fail to give you any catalog feed at all (With “There is no product feed available. Try run the scheduled job first.” error, even if you already ran the Export Catalog Feed job. There is currently no known fix for that). Is there a way to simply get the data?

Yes, there is. It’s not fancy, but it works. All your catalog feeds will be put in appdata\blobs\d4a76096689649908bce5881979b7c1a folder, so just go there and grab the latest ones. appdata is the path defined in your <episerver.framework>\<appData> section.

What if you are running on Azure? It is the same “folder”. Use Azure Storage Explorer to locate the blob. If you are running on DXP, get in touch with developer support service and they’d be happy to help.

I originally planned to write a small tool to easily download the catalog feeds, but it turned out the Episerver Blob APIs have no way to list content of a container, so a manual, simple way is better this time.