Skip to main content

7 posts tagged with "Architecture"

Layer boundaries, dependency direction and the design decisions you pay for as a system grows.

View All Tags

Singleton, Scoped or Transient? Get It Wrong and Your DbContext Lives Forever

· 13 min read
Nguyễn Huỳnh Minh Tiến
Middle Fullstack Developer @ Utop.vn
Summary

Choosing a lifetime means choosing when the container creates an instance and when it disposes it. Transient creates a new one on every resolve, Scoped gives one instance per HTTP request, Singleton one instance for the whole process. The single most important rule: a long-lived service must not take a short-lived one directly. Inject a Scoped repository into a Singleton and you have just kept that DbContext alive until the app shuts down. The fix is to inject IServiceScopeFactory and open a scope yourself when you need one.

The registration line that causes this looks so harmless that nobody pauses on it in review:

builder.Services.AddDbContext<CrmDbContext>(o => o.UseSqlServer(conn)); // Scoped
builder.Services.AddScoped<ICustomerRepository, EfCustomerRepository>();
builder.Services.AddSingleton<CustomerCacheRefresher>(); // ← the explosive

The app passes CI and reaches staging. Once it is under real load, the logs start showing A second operation was started on this context instance before a previous operation completed, or Cannot access a disposed context instance — or, worse, no exception at all and merely stale data that never changes. This post explains the mechanism behind it, with the code for both the broken and the fixed version.

Building a Multi-Tenant CRM Platform from Nothing: 20 Months on the Job

· 30 min read
Nguyễn Huỳnh Minh Tiến
Middle Fullstack Developer @ Utop.vn
Summary

In January 2025 I was handed a product to initialise — the first time in my career — a multi-tenant CRM platform whose repository was completely empty when I took it on. For nearly its first year it lived in demo mode, shown to customers across many different industries, until late 2025 when a healthcare client signed and it moved into real delivery. Twenty months later it is 18 microservices on ABP Framework / .NET 9 and 13 Angular libraries, built by a team that grew to 25 people. This is not a technical article but a career story told chronologically, and it includes the export screen that forgot its permission checks, the two weeks of Angular work I had to revert, the three months mid-year when I was moved to a different loyalty project and committed nothing, and a migration so boring that nobody noticed it had happened. It also covers the EAV architecture that lets customers configure their own data fields from the portal, along with the months I spent wrestling with its performance and that of the dynamic filter sitting on top of it, the stretch where I mentored four interns for the first time, and how the way I write code shifted from typing everything by hand to ChatGPT, then Cursor, then Claude.

On 14 January 2025 I opened a completely empty repository with nothing in my head but a handful of very ordinary questions: what to name the solution, which folder goes where, how to split the database tables.

Until then I had always inherited codebases that were already running, where the work was reading, fixing and adding screens. This felt entirely different: there was nothing to copy, and whatever I typed would become the thing everyone else followed. It sounds impressive in the telling, but sitting in front of that empty repo I was genuinely a bit overwhelmed.

My commit that day was a single line, Init - Databases, projects, and there was nothing ceremonial about it.

Twenty months later that repo holds 18 microservices with 29,607 commits from 25 people. I am still there, and every so often I still open git log --reverse to look at those first few lines.

git log --reverse --format="%h %ad %an %s" --date=short | head -5

I am writing this to read again in a few years. The product belongs to the company, so I have anonymised all the proper nouns — the product name, the repo name, the client, the internal libraries — while leaving the tech stack intact, since that is the part I can tell.