On a personal note last week was a busy week. I don't expect this one to be any slower but it is important to take some time off and relax. I try to keep telling myself this. Time permitting I will try to incorporate some small code snippets in these posts just to keep it fun. This blog is a running work log so I won't get into too many personal notes (no politics). I found a set of photos of welders that are pretty cool. They build structures and tools which is analogous to many of the things I do.
We built a multi-tenant application on Sql Server and some of the requirements coming out now are about building some cross tenant billing reports. Until there is some kind of data warehousing solution in place (future), this type of problem is challenging and fun. The architecture uses the wonderful Service Stack Ormlite library which makes querying over these databases simple. You just register the connection and then you can loop over each one and query them. This week I built the main report needed to do some billing activities including some methods to easily run queries across all databases. At some point this will have to be modified to run in a scheduling tool like Hangfire (mentioned below) and the report saved as a blob with an email instead of doing it in process. We will get to that code when the time comes.
Here are a few code snippets with regards to multi-tenancy:
// register each connection (typically done in the appconfig's configure:
foreach(var companyAccount in companyAccounts)
{
f.RegisterConnection(companyAccount.Id.ToString(), companyAccount.SqlDataBase.ConnectionString, SqlServer2019Dialect.Provider);
}
// This is how you'd use the connections to loop over them and build a report. See below for a better example
foreach(var key in OrmLiteConnectionFactory.NamedConnections.Keys)
{
using (var db = f.OpenDbConnection(key))
{
var list = db.Select<Report>();
}
}
// it gets a little more fun with a full example of usage. Assuming registrations done like above
// This is the goal, to make it easy to run a query across all db's
public class ReportService : BaseService
{
public object Any(SomeRequest req)
{
Func<IDbConnection,int, List<SomeResponse>> fn = (db, dbId) =>
{
return db.Select<SomeResponse>(x=>x.dbId==dbId);
}
return RunOnAllConnections(fn);
}
}
// This is how to do it:
public class BaseService : Service
{
public IDbConnectionFactory dbConnectionFactory {get;set;}
public Dictionary<string, OrmLiteConnectionFactory>.KeyCollection ConnectionKeys {get
{
return OrmLiteConnectionFactory.NamedConnections.Keys;
}
}
public List<T> RunOnAllConnections<T>(Func<IDbConnection,int, List<T>> fn)
{
List<T> items = new List<T>();
foreach(var key in ConnectionKeys)
{
// in my case the key is a string representation of a company id (int)
int dbId = Int32.Parse(key);
using (var db = dbConnectionFactory.OpenDbConnection(key))
{
items.AddRange(fn(db, dbId));
}
}
return items;
}
}
Continue helping a client with their legacy app migration to an Azure Web App/Sql Azure. Part of this challenge was the removal of some cross database queries/views/etc which are not really supported by SQL Azure and migrating a windows service to hangfire to keep it simple. In case you may have never seen Hangfire, it is a scheduling library that can use SQL Server, Redis, or even an in memory data store to schedule and run tasks. Let's say the user saves some kind of object and some email notification should be sent in a week.
void Save(MyObject obj)
{
obj.Expires = DateTime.UtcNow.AddDays(7);
Db.Save(obj);
BackgroundJob.Schedule<MyClass>(x=>x.SendNotfication(obj.Id), obj.Expires); // will execute on the expire date/time
}
Continue work for another application to allow offline syncronization and usage.
I used the moment.js library in many projects over the years. Recently it has been placed in maintenance mode and for new projects I wouldn't recommend its use. Here is a good article about some alternatives.
Attended the Umbraco New Year Reception 2021 meeting to get clarification on Umbraco v8 -> vNext upgrade path. No questions were answered on the call even though they asked to have questions emailed in.
Researching some BigCommerce limitations. How do you restrict one product from being ordered with another? It isn't possible.
Vendor vs Partner. Through the years we used tons of different vendors for many different services. We have been vendors as well for clients. I noticed that perhaps as I get older (kidding) the level of support becomes much more important than the cost (assuming it is reasonable). In my notes from last week there is mention of an email service provider that has lousy support. This week, I tried to turn off some old hosting and their support is non responsive as well. More than a week has gone by and I asked about it several times with no response. This level of interaction is really not acceptable and all these companies have to do is acknowledge the support request and provide a date/time when they will respond. Azure does a pretty good job of this as well as a few other companies we are engaging with recently but the more I encounter non responsive support tickets it just pushes me away from ever recommending or doing business with them again.
More SMS vendor calls.
Reviewed some Umbraco code to see if there was some straightforward solution to this issue. What I ended up finding in the code path that generates that issue was a method that has no comments, half is commented out and is NOT very clear how it works. FindPublishedContent is obviously mutating the request object and the same line is repeated before and after (request.IsRedirect)? 🤔 It is really hard to get contributors when there is no documentation or comments.
/// <inheritdoc />
public bool TryRouteRequest(PublishedRequest request)
{
// disabled - is it going to change the routing?
//_pcr.OnPreparing();
FindDomain(request);
if (request.IsRedirect) return false;
if (request.HasPublishedContent) return true;
FindPublishedContent(request);
if (request.IsRedirect) return false;
// don't handle anything - we just want to ensure that we find the content
//HandlePublishedContent();
//FindTemplate();
//FollowExternalRedirect();
//HandleWildcardDomains();
// disabled - we just want to ensure that we find the content
//_pcr.OnPrepared();
return request.HasPublishedContent;
}
I have a list of items such as plugins, nuget packages, etc that I track (even if mentally) because they might be missing something. For instance, Umbraco's upgrade to .net core, or the Google Map Flutter implementation for clustering. Google really dropped the ball on the Flutter plugins in the past so I am hoping they are picking it up because some packages like Google Maps is unusable in many situations without Clustering and that linked ticket and Pull Request is almost two years old.
Onboarding a new client on Monday and we needed some admin utilities to massage their data.
Fixed some DNS issues on a client's website. Not sure what they were trying to do but all good now. 🤞
Week 3 was slightly less busy but good. Looking forward to week 4. Have a nice weekend.