开发者
GraphQL Query & Mutation Architecture, A Production Deep Dive
Author: Erwin Wilson Ceniza Published: July 2, 2026 Tags: GraphQL | HotChocolate | BatchDataLoader | CQRS | Outbox Pattern | Apollo Federation | .NET | Architecture | EMR | API Design GraphQL Query & Mutation Architecture - A Production Deep Dive Code-first GraphQL with HotChocolate, BatchDataLoader, CQRS, and a transactional outbox pattern, with real examples from a production EMR system serving three portals from a single schema. Table of Contents Interactive Data Traversal The Architecture at 30,000 Feet Why GraphQL Won for Healthcare Data Shapes Simple Queries vs. Complex REST, The Comparison That Sold Me The Resolver Layer, Code-First, Schema-Last How GraphQL Smoothly Orchestrates the Application Services N+1 Is the Silent Killer, How BatchDataLoaders Eliminate It Mutation Architecture - CQRS + Transactional Outbox Security at the Resolver Level, Custom Middleware Attributes Type Extensions, Why I Stopped Writing DTO Mappers Projections, Filtering, Sorting, and Paging Apollo Federation, Future-Proofing the Graph GraphQL Client on Mobile, Sharing Query Logic Across Portals Why I Chose Ionic for the Patient Mobile App The Retrospective, What I'd Keep and What I'd Change A step-by-step walkthrough of how the app consumes (reads) and creates (writes) data through the services. interactive <script type="module"> const C = document.currentScript.parentElement; C.style.cssText='width:100%;font-family:system-ui,-apple-system,sans-serif'; const S = document.createElement('style'); S.textContent=` .gv *{box-sizing:border-box;margin:0;padding:0} .gv{background:var(--bg-secondary,#111);border-radius:12px;overflow:hidden;border:1px solid var(--border,#333);min-height:440px} .gv-tabs{display:flex;border-bottom:1px solid var(--border,#333);background:var(--bg-card,#1a1a1a)} .gv-tab{flex:1;padding:12px 8px;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;cursor:pointer;border:none;background:none;color:var(--text-muted,#666);transition:all .2s;border
AI 资讯
Back to Simplicity: Why We Built CALM, a Lock-Free Single-Thread Messaging Library for .NET
Hello everyone! For many years, I have been developing equipment control software and long-term support products using .NET/C#. Based on the experiences gained through working with various development teams, I would like to talk about why I created CALM (Cooperative Async Lock-free Messaging) , an open-source library for .NET. The Magic of UI Thread + Event-Driven + async/await My journey with .NET/C# began around the days of .NET Framework 4.0. At the time, code-behind in Windows Forms was incredibly intuitive. It allowed me to join the development team and become productive in a very short period. Back then, executing time-consuming operations on a separate thread and returning the results to the UI thread via callbacks was painful and error-prone. However, the introduction of async/await in .NET Framework 4.5 completely shifted the paradigm. The SynchronizationContext magically handled thread marshaling behind the scenes, and our code became amazingly simple. "Running asynchronous operations safely on a single thread (the UI thread) via an event-driven approach" — this seamless developer experience was my starting point. Divide and Conquer, Dependency Management, and CQS As our product evolved, the codebase grew, and the development team expanded beyond a certain size. Suddenly, the software became exponentially complex. Following industry best practices, we introduced MVP and MVVM patterns to separate the UI from the business logic. We also refactored our domain models based on Domain-Driven Design (DDD) and Clean Architecture principles, alignment with the team's domain knowledge. While this helped organize the logic within individual models, it introduced a new nightmare: complex dependencies between models. We struggled heavily with initialization, especially with models that had circular or mutual dependencies. To break this web of tight coupling, we introduced a mechanism that combined the Observer pattern (inspired by Android's EventBus) with the philosoph
AI 资讯
Ensuring Thread Safety — .NET core-centric
Prefer immutability What: Make data read-only after construction. Instead of editing objects, create new ones. Why: If nothing changes, many threads can read safely with no locks . How (.NET): public readonly record struct Money ( decimal Amount , string Currency ); public record Order ( Guid Id , IReadOnlyList < OrderLine > Lines ) { public Order AddLine ( OrderLine line ) => this with { Lines = Lines . Append ( line ). ToList () }; } Use record / readonly struct , IReadOnlyList<> , and with (copy-on-write). Keep collections immutable ( ImmutableList<T> , ImmutableDictionary<K,V> ). Avoid shared state What: Don’t let unrelated code touch the same mutable object. Why: If each operation owns its data, there’s nothing to synchronize. How: Per-request scope : create new service instances that hold request-specific state. No static mutable fields; if you must cache, use ConcurrentDictionary : private static readonly ConcurrentDictionary < string , Widget > _cache = new (); var widget = _cache . GetOrAdd ( key , k => LoadWidget ( k )); Use lock / SemaphoreSlim cautiously What: Synchronization primitives that serialize access to critical sections. When: Short, minimal critical sections where mutation is unavoidable. lock for synchronous code; SemaphoreSlim when await is involved (never block in async code). Patterns & pitfalls: private readonly object _gate = new (); void Update () { lock ( _gate ) // keep work tiny inside { // mutate a small piece of shared state _count ++; } } private readonly SemaphoreSlim _sem = new ( 1 , 1 ); async Task UpdateAsync () { await _sem . WaitAsync (); try { _count ++; } finally { _sem . Release (); } } Never lock(this) or a public object (external code could deadlock you). Keep lock duration short; avoid I/O under locks. If multiple locks are needed, fix a global order to prevent deadlocks. Atomic counters (avoid locks entirely): Interlocked . Increment ( ref _count ); Leverage actor-style or message queues What: Push work as messages to
AI 资讯
Synchronous vs asynchronous in .NET core - how decide
Rule of thumb If your action waits on something external , make it async . If it’s instant CPU , keep it sync ; for expensive CPU , offload . The core idea Async shines for I/O-bound work (DB calls, HTTP calls, queues, files). It frees the request thread while waiting, so the server can serve more requests with the same thread pool . Sync is fine for trivial, short CPU work (formatting, small calculations) where you’re not awaiting anything and the handler returns in a few milliseconds. When to choose async You call EF Core ( SaveChangesAsync , ToListAsync ), HttpClient , Azure SDK ( ServiceBusClient , BlobClient ), file I/O, or any API with Async methods. You expect latency from a dependency (tens–hundreds of ms). You need cancellation and timeouts (propagate HttpContext.RequestAborted ). When sync is acceptable The action is pure CPU and trivial (e.g., quick math, mapping, input validation) and returns immediately. There are no I/O waits and no benefit from freeing the thread. If it’s CPU-heavy (image processing, big JSON transforms), do not just make it async—offload to a background queue/worker or a separate compute service. Async won’t make CPU faster. Pitfalls to avoid Don’t block async : never use .Result / .Wait() on Tasks (deadlocks/thread-pool starvation). Async all the way down : if the controller is async, downstream calls should be too. Don’t fake async : returning Task.Run around synchronous I/O just burns threads. Keep concurrency bounded when fanning out to multiple I/O calls. Mini decision checklist Any I/O? → Use async (end-to-end). Pure CPU? Tiny (≤ a few ms) → Sync is fine. Heavy/variable → Offload to background worker; controller returns 202/Location or uses a queue. ASP.NET Core examples Async (I/O-bound) — recommended [ ApiController ] [ Route ( "orders" )] public class OrdersController : ControllerBase { private readonly OrdersDbContext _db ; private readonly HttpClient _http ; public OrdersController ( OrdersDbContext db , IHttpClientFactory
开发者
Service Discovery in Modern .NET Applications and Azure
This article is part of the Comprehensive Guide to Microservices Architecture in .NET Core, Cloud and Azure series. Service Discovery in Kubernetes How Kubernetes DNS Works Kubernetes automatically creates DNS entries for services, enabling simple name-based discovery. A service named order-service in the production namespace becomes accessible at order-service.production.svc.cluster.local . For services within the same namespace, you can use the short name order-service . Service Definition: apiVersion : v1 kind : Service metadata : name : order-service namespace : production labels : app : order-service version : v1 spec : selector : app : order-service ports : - name : http protocol : TCP port : 80 targetPort : 8080 type : ClusterIP Native Service Discovery in .NET 9 .NET 9 introduces enhanced service discovery capabilities with improved configuration and resilience features. Basic Configuration: // Program.cs var builder = WebApplication . CreateBuilder ( args ); // Add service discovery with .NET 9 enhancements builder . Services . AddServiceDiscovery (); // Configure HTTP client with service discovery builder . Services . AddHttpClient < IOrderServiceClient , OrderServiceClient >( client => { // Use service name - discovery resolves to actual endpoint client . BaseAddress = new Uri ( "http://order-service" ); }) . AddServiceDiscovery () . AddStandardResilienceHandler (); // .NET 9 resilience patterns var app = builder . Build (); Advanced Configuration with appsettings.json: { "ServiceDiscovery" : { "Providers" : { "Kubernetes" : { "Namespace" : "production" , "RefreshPeriod" : "00:01:00" } }, "Services" : { "order-service" : { "Scheme" : "https" , "EndpointNames" : [ "http" , "https" ], "HealthCheckPath" : "/health" }, "payment-service" : { "Scheme" : "http" , "AllowAllHosts" : false } }, "AllowAllHosts" : true , "AllowedHosts" : [ "*.svc.cluster.local" ] } } Service-to-Service Communication Patterns Using Typed HTTP Clients: public interface IOrderServiceCli
AI 资讯
Service Communication Patterns in .NET Core and Azure
This article is part of the Comprehensive Guide to Microservices Architecture in .NET Core, Cloud and Azure series. Asynchronous Messaging with Azure Service Bus Azure Service Bus provides enterprise-grade messaging infrastructure with advanced features for reliable message delivery, ordering guarantees, and complex routing scenarios. Service Bus vs Azure Queue Storage Azure Service Bus offers enterprise messaging capabilities including: Topics and subscriptions for pub/sub patterns Message sessions for ordered processing Transaction support across operations Dead-letter queues for failed messages Messages up to 100MB (premium tier) Advanced routing with filters and actions Azure Queue Storage provides: Simple FIFO queue operations Lower cost for basic scenarios Messages up to 64KB Best for simple point-to-point messaging When to Choose Service Bus Use Azure Service Bus when you need: Publish-subscribe patterns with multiple subscribers Guaranteed message ordering with sessions Transactional message processing Message size beyond 64KB Advanced routing and filtering Integration with hybrid or on-premises systems Implementation with .NET 9 .NET 9 introduces improved performance and simplified APIs for working with Azure Service Bus: // Producer using .NET 9 with improved performance public class OrderCreatedPublisher { private readonly ServiceBusSender _sender ; public OrderCreatedPublisher ( ServiceBusClient client ) { _sender = client . CreateSender ( "order-events" ); } public async Task PublishOrderCreatedAsync ( Order order , CancellationToken cancellationToken = default ) { var message = new ServiceBusMessage ( JsonSerializer . Serialize ( order )) { MessageId = order . OrderId . ToString (), Subject = "OrderCreated" , ContentType = "application/json" , // .NET 9: Better support for distributed tracing ApplicationProperties = { [ "CorrelationId" ] = Activity . Current ?. Id ?? Guid . NewGuid (). ToString (), [ "OrderDate" ] = order . CreatedAt . ToString ( "O" )
AI 资讯
API Gateway Patterns in .NET Core and Azure
This article is part of the Comprehensive Guide to Microservices Architecture in .NET Core, Cloud and Azure series. API gateways serve as the entry point for client applications in distributed architectures, handling request routing, composition, and protocol translation. As systems grow more complex with multiple client types and backend services, choosing the right gateway pattern becomes crucial for maintaining performance, scalability, and developer productivity. This article explores two powerful API gateway patterns in .NET: the Backend for Frontend (BFF) pattern, which creates client-specific gateways, and GraphQL, which offers flexible, query-driven data fetching. Both patterns address the challenge of efficiently serving diverse clients while maintaining clean architecture and optimal performance. Backend for Frontend (BFF) Pattern Web BFF Implementation The web BFF returns detailed, enriched data suitable for desktop browsers with higher bandwidth and processing power: [ ApiController ] [ Route ( "api/web/[controller]" )] public class OrdersController : ControllerBase { private readonly IOrderServiceClient _orderClient ; private readonly ICustomerServiceClient _customerClient ; private readonly IInventoryServiceClient _inventoryClient ; [ HttpGet ( "{id}" )] public async Task < WebOrderDto > GetOrder ( Guid id ) { // Execute parallel calls to improve response time var orderTask = _orderClient . GetOrderAsync ( id ); var customerTask = _customerClient . GetCustomerAsync ( id ); var inventoryTask = _inventoryClient . GetInventoryStatusAsync ( id ); await Task . WhenAll ( orderTask , customerTask , inventoryTask ); return new WebOrderDto { Order = orderTask . Result , Customer = customerTask . Result , InventoryStatus = inventoryTask . Result , // Include additional rich data for enhanced web UI experience RecommendedProducts = await GetRecommendationsAsync ( id ) }; } } Mobile BFF Implementation The mobile BFF provides optimized, lightweight responses to min
AI 资讯
Part 14: Community and Ecosystem - Contributing to Vyshyvanka
It is clear that Vyshyvanka is more than just code — it is an ecosystem. The true power of an open-source workflow engine lies in its community. Today, we want to talk about how you can get involved, whether you are interested in pushing the boundaries of the core engine or building specialized solutions with custom plugins. The Core Engine vs. The Plugin Ecosystem A common question we get is: 'Should I contribute a PR to the core engine, or should I build a separate plugin?' The answer depends entirely on the scope of your contribution. When to Contribute to Core The core engine ( Vyshyvanka.Core , Vyshyvanka.Engine , Vyshyvanka.Api , Vyshyvanka.Designer ) should be reserved for changes that benefit every user of the platform. Good candidates for core contributions: Performance improvements to the execution pipeline New fundamental port types or expression functions Bug fixes in the engine, validation, or persistence layers Enhancements to the Designer UI (canvas, node editor, property editors) Improvements to the API surface (new endpoints, better error responses) Documentation improvements These changes require careful review and testing because they impact every installation. We encourage PRs here, but we also ask that you open an issue first so we can discuss the architectural impact. When to Build a Plugin Plugins ( ./plugins/ ) are the best way to extend functionality without increasing the maintenance burden of the core. Good candidates for plugins: Integration with a specific third-party SaaS tool (CRM, CI/CD, monitoring) Custom nodes specific to your industry or use case Experimental node behaviors that are not yet ready for core Proprietary integrations you want to keep separate from the open source project Plugins are independent, versionable, and can be maintained outside the core release cycle. They empower you to solve your specific problems immediately without waiting for a core release. Project Structure at a Glance Understanding where things live i
AI 资讯
Importing users without a password reset
Every identity migration guide eventually reaches the same paragraph, and it's always a little apologetic: "users will need to reset their passwords." It gets treated like a law of nature. It isn't. It's a choice, usually forced by a tool that didn't want to do the harder thing. The harder thing is verifying your users' existing password hashes in place, so they sign in after the move with exactly the credentials they had before and never notice anything happened. Whether you can do it comes down to one question: can you get the old hashes, and can the new system verify them? Password hashes are more portable than people think A password hash isn't a secret algorithm. bcrypt is bcrypt. A bcrypt hash carries its own cost factor and salt inside the string, so anything that implements bcrypt can verify a hash any other bcrypt system produced. The same is true of the PBKDF2 format ASP.NET Identity uses: documented, versioned, self-describing. If you know what you're holding, you can check a password against it without ever knowing the password. So a migration that preserves logins doesn't need the plaintext (nobody has it) and doesn't need to re-hash everyone up front. It needs to obtain the stored hashes and verify against them on sign-in, upgrading each one to its own format quietly the first time a user logs in. That last part is lazy migration: carry the old hash, verify it once, replace it transparently. Over a few weeks of normal logins your user table re-hashes itself and the legacy formats age out, with zero resets and zero support tickets. The dual-path bit The wrinkle is that different sources hand you different formats, and a good importer verifies both: From self-hosted Duende / ASP.NET Identity: the V3 PBKDF2 hashes (and any legacy bcrypt) verify natively and rehash on first sign-in. This is the easy case, because it's the same scheme the destination already uses. Most teams are surprised it's that clean. From Auth0: bcrypt hashes verify verbatim. The catch
AI 资讯
Data-Oriented Design in C#: Why Objects Are Slowing You Down
Data-Oriented Design in C#: Why Objects Are Slowing You Down In my previous article, we talked about starving the Garbage Collector by moving away from heap-allocated class types and leaning heavily into struct , Span<T> , and ArrayPool<T> . That’s a critical first step, but it only solves half the problem. You’ve stopped the GC from pausing your app, but you might still be leaving massive amounts of CPU performance on the table. Why? Because of how your data is structured. It’s time to talk about Data-Oriented Design (DoD) . The Object-Oriented Trap We are taught from day one to model our code after the real world. If you are building a social network graph, you might write something like this: public class UserNode { public int Id { get ; set ; } public string Name { get ; set ; } public List < Edge > Connections { get ; set ; } } public class Edge { public UserNode Target { get ; set ; } public int Weight { get ; set ; } } This makes perfect logical sense. A user has connections, and those connections point to other users. But modern CPUs don't care about your logical models. A CPU only cares about reading data from memory into its L1/L2 caches as fast as possible. When a CPU reads a byte from RAM, it doesn't just read that one byte; it pulls a whole 64-byte "cache line" under the assumption that you will probably want the neighboring bytes next. When you loop through a List<UserNode> , traversing from object to object, you are jumping randomly across the heap. The CPU pulls a cache line, reads your data, and then has to go fetch a completely different block of RAM for the next node. This is called pointer chasing , and the resulting cache misses are devastating to performance. Enter Data-Oriented Design: Struct of Arrays (SoA) Data-Oriented Design says: Stop modeling the real world. Model the data the way the hardware wants to consume it. Instead of an Array of Structs (AoS) (or an array of objects), we invert the architecture to a Struct of Arrays (SoA) . If we
AI 资讯
We log into our own admin console with our own SAML. Here's what it caught.
There's a version of dogfooding that's a slogan, and a version where your own employees can't ship code until the bug is fixed. We run the second kind. The Authagonal staff console, the one we use to manage every tenant, authenticates through Authagonal itself: SAML single sign-on from our Entra directory, with SCIM deciding who gets in and what they can do. There is no separate admin password table. We deleted it. If our own SAML is broken, we are locked out of our own product. That's uncomfortable in exactly the useful way. It turns "SSO is an enterprise feature we support" into "SSO is the only way the people who built this get to work today." Here's what being our own customer caught. A trimmed build that broke signature verification We publish the auth server trimmed, to keep the image small. Trimming aggressively deletes code it can't prove is used, and reflection hides usage from it. .NET resolves its XML-signing crypto algorithms by name, reflectively, through CryptoConfig . The trimmer couldn't see those types were needed, removed them, and SignedXml quietly came back unable to build the algorithm. SAML signature verification, the step that proves the login is real, threw a null reference at runtime. The unit tests passed, because they ran against the untrimmed build where the types still existed. Only the trimmed production artifact failed, and it failed at the exact moment a human tried to sign in. We ship the auth server untrimmed now, with a healthy distrust of trimming anything near reflection-based crypto. If we only supported SAML rather than living on it, this is a customer's incident report instead of ours. Provisioning is the actual login Authenticating a user is the easy half of SSO. The hard half is deciding what they're allowed to do and keeping it in sync as people join and leave. We drive that with SCIM: Entra group membership maps to roles, resolved at the moment a token is issued, not copied once at account creation. Add someone to the righ
AI 资讯
Azure Key Vault: Where Every Secret in This Blog Actually Lives
I have written some version of "never hardcode secrets, store them in Key Vault instead" in at least five of my last nine posts on this blog. I never actually stopped to explain what that means in practice. This post fixes that, using the real secrets this very blog depends on: a database connection string, an admin panel password, and a set of GitHub deployment credentials. What Azure Key Vault Actually Is Azure Key Vault is a managed service for storing secrets, encryption keys, and certificates securely in the cloud. Instead of a password sitting in a configuration file or a public GitHub repository where anyone with read access can see it, the password lives in Key Vault - encrypted, access-controlled, and logged every single time it is read. Picture your code as a house with see-through walls - anyone looking at the repository can see everything inside, including any password left lying on the kitchen table. Key Vault is a bank vault a few streets away. Your code does not hold the password directly; it holds a key card that lets it walk over and request the password at the exact moment it is needed. Lose the key card, and you still cannot get into the vault without proper identity verification on top of it. Three Things Key Vault Stores Secrets are plain string values - passwords, connection strings, API keys, tokens. Keys are cryptographic keys used for encrypting and decrypting data, or for signing and verifying it. Certificates are X.509 certificates used for TLS or client authentication between services. Most applications, including this one, primarily use the Secrets feature. An Honest Admission About TechStackBlog's Own Setup This blog does not actually use Key Vault directly today. The database password lives in Azure App Service Configuration. The admin panel password and deployment credentials live in GitHub Secrets. For a single-application personal project, this is a perfectly reasonable and secure setup. Key Vault earns its place once you have multi
AI 资讯
Clean Architecture in .NET 8: A 2026 Starter Template with 4 Projects, EF Core, and JWT Auth
I joined a team where the controller was 800 lines long, the business rules were scattered between the controller and the DbContext , and "to run the tests, spin up a SQL Server in Docker" was a sentence I heard every week. The fix was Clean Architecture. The argument I had with the team lead was about how to actually structure it. We argued for two weeks. Then I built this template so the next person wouldn't have to. This is the Clean Architecture .NET 8 starter template I wish someone had handed me on day one. Four projects, strict dependency direction, domain entities that own their own invariants, and an Application layer you can unit test with Moq — no database required. The whole repo is on GitHub , MIT-licensed, runs with dotnet run , and ships with xUnit tests, JWT auth, Swagger, Docker, and CI. This post is the explanation of why each project exists, what goes in it, and what I learned the hard way about getting Clean Architecture right in .NET. The problem Clean Architecture solves The naive way to build a .NET Web API is one project, one folder structure, and "everything talks to everything": MyApp/ Controllers/ ProductsController.cs ← HTTP stuff OrdersController.cs ← HTTP stuff + business rules Services/ ProductService.cs ← business rules + DbContext.SaveChanges Data/ AppDbContext.cs ← EF Core, entities Models/ Product.cs ← POCO with public setters This works for the first 1,000 lines. By 5,000 lines, the controller is doing five things at once. By 10,000, "to test this, I need a database" is the answer to every test question, and your CI takes 20 minutes because every test run spins up SQL Server. Clean Architecture says: separate the business rules from the HTTP boundary, separate the database from the business rules, and enforce it with project references. A controller is allowed to call a service. A service is allowed to call a repository. A repository is allowed to know about EF Core. Nothing is allowed to know about anything "above" it in the chai
AI 资讯
Build a Local RAG Chatbot in 30 Minutes with .NET 8, Ollama, and React
I uploaded a 40-page PDF of an internal API spec, asked "what's the rate limit for the search endpoint?", and got back: "100 requests per minute per API key, with bursts up to 200. See section 4.2 of the document." With citations. In about three seconds. The whole stack runs on my laptop. It cost me $0 in LLM credits during development because Ollama is free and local, and the embedder I used is also free and local. The repo is here — issues and PRs welcome. This is the build log. Not a tutorial where every step works the first time — a build log where I tell you which decisions held up and which ones I redid. The problem most "chat with your PDF" demos have Every "chat with your PDF" tutorial I read in early 2025 had the same shape: open OpenAI, paste your API key, call gpt-4 with a 50-page PDF stuffed into the context window, get an answer, pay $0.03 per question, repeat. That works for a demo. It does not work for a tool you'd actually use at work, because: The PDF might contain customer data, internal pricing, or unreleased features. You do not want that going to OpenAI's training pipeline or anyone's logs. The cost adds up. If your team uses it 50 times a day, that's $45/month per seat. The model hallucinates on long PDFs anyway. Stuff 100 pages into a 128k context window and the model starts forgetting the middle. The fix is RAG (Retrieval-Augmented Generation) — don't send the whole PDF, send only the 3-5 chunks that are actually relevant to the question. The rest of the work is the same: embed the chunks, embed the question, find the closest matches, send those to the LLM with the question. But the cost and the privacy story both improve by 100x. The actual ask: Upload a PDF. Ask questions. Get answers from the document with citations, in under 5 seconds, with no data leaving my laptop and no monthly bill. The architecture One .NET 8 solution, one React app, one Ollama process, zero cloud dependencies. [ PDF Upload ] | v +-------------------+ chunks +-------
AI 资讯
From Stack Trace to Suggested Fix in 4 Seconds: Building a Self-Healing .NET API Gateway.
Last Tuesday my API gateway caught a NullReferenceException , streamed it to a dashboard in real-time, and pushed a draft code fix to the browser tab of the on-call engineer — before I finished reading the error myself. That sentence used to be vendor marketing. Now it's just my Program.cs . This is the architecture post-mortem. I built it on weekends. It runs in Docker. It cost me exactly $0 in LLM credits during development because Groq's free tier is generous and Ollama works as a swap-in. The repo is here — issues and PRs welcome. The problem most .NET teams have Production errors are caught, logged to a file, and forgotten. Engineers find out from a Slack ping twenty minutes later, if at all. By the time someone looks, the original request context is gone, the user's session has expired, and the stack trace is buried four layers deep in System.* calls. "Self-healing" is a word vendors use to mean "auto-restart the pod." I wanted something better. The actual ask: When an exception is thrown in service A, give the engineer (a) a clear root cause, (b) a suggested fix, and (c) a draft code patch — in under 30 seconds. Not a magic black box. Not an auto-applied patch. Just: catch the error, give the model the right context, push the analysis to a human in real-time, and let the human close the loop. The architecture One .NET solution, four projects, four NuGet packages, no new infrastructure beyond what you probably already have. [ HTTP request ] | v +-------------------+ enqueue +---------------------+ | SmartLogAnalyzer. | ---------------------> | Hangfire (Redis) | | Api | +----------+----------+ | (ErrorHandling | | | Middleware) | v +-------------------+ +---------------------+ | SmartLogAnalyzer. | | Worker | | (ErrorProcessingWorker) +-----+-------+-------+ | | AI call | | persist v v +-----------+ +-----------+ | Semantic | | MSSQL | | Kernel + | | (ErrorLog | | Groq LLM | | table) | +-----+-----+ +-----------+ | v +---------------------+ | SignalR Hub | | (
AI 资讯
HelmSharp: render Helm charts from .NET without shelling out to helm
TL;DR: I built a .NET library that renders Helm charts and drives Kubernetes releases without shelling out to the helm CLI. 129/129 templates across ingress-nginx, cert-manager, external-dns, podinfo, and metrics-server now render successfully. The main entry point is HelmSharp.Action, with lower-level packages available for chart loading, rendering, Kubernetes operations, and release storage. MIT licensed, looking for feedback and early adopters. Why I Built This At work, our .NET services deploy to Kubernetes through Helm. Every Docker image had to bundle the helm binary — another dependency to manage, another layer in the image, another surface for CVEs. I wanted to cut that out entirely and do Helm-style rendering directly in-process. The .NET ecosystem doesn't really have this. There are YAML libraries. There are Kubernetes client libraries. There are template engines. But nothing ties them together the way helm template does — values merging, named templates, include , range , toYaml , the whole Sprig function set, all wired into a single render pipeline. So I started building one. (This is also my first real open source project — I'd spent years consuming OSS without contributing back, and HelmSharp is what came out of deciding to change that.) What HelmSharp Does HelmSharp is a multi-package .NET SDK (net8.0 / net9.0 / net10.0) that covers: Package What it does HelmSharp.Action High-level Helm client — TemplateAsync , UpgradeInstallAsync , RollbackAsync HelmSharp.Chart Chart loading from directories and .tgz , values merging, --set / --set-json style overrides HelmSharp.Engine Helm-style template rendering — 100+ Sprig/Helm functions HelmSharp.Kube Kubernetes apply, delete, and wait (no kubectl needed) HelmSharp.Release Release history stored in Kubernetes Secrets (Helm-compatible) HelmSharp.Repo Chart repository index, pull, and search Plus Registry , Storage , PostRenderer extension points Here's the lower-level rendering API — no result objects, no stdout
AI 资讯
Meet AppPipe: The Lightweight, On-Premises Alternative to .NET Aspire
Modern cloud-native developer environments are fantastic. Frameworks like .NET Aspire have revolutionized local development by providing a unified developer dashboard, automatic service discovery, and OTLP telemetry collection. But what happens when it's time to deploy your microservice topology on-premises? If your target is IIS on Windows Server or a systemd service on Linux , you've likely realized that deploying the standard .NET Aspire stack on-prem is a complex puzzle. There is no native hosting model for IIS, gRPC telemetry port mapping is fragile, and the dashboard's constant WebSocket connections can consume excessive resources on on-premises virtual machines. Enter AppPipe.Hosting —a lightweight, developer-friendly NuGet package designed specifically to bring the best features of .NET Aspire to your on-premises environments. The Problem: On-Premises Microservice Orchestration is Hard While cloud platforms have native orchestration (like Kubernetes or ECS), traditional Windows and Linux environments still host a massive volume of enterprise applications. When running microservices on IIS or Linux servers, developers face three major friction points: Port Conflicts & Service Discovery : Dynamically assigning ports to multiple microservices in IIS or systemd and injecting them into dependent services is tedious. Telemetry Aggregation : Running an OpenTelemetry Collector just to aggregate traces, logs, and metrics for a small on-prem cluster is heavy and complex to configure. Resource Exhaustion : Standard Blazor Interactive Server dashboards maintain constant WebSockets and high memory usage, which quickly drains limited hosting environments. The Solution: AppPipe AppPipe is a lightweight alternative that integrates a routing gateway, an in-memory telemetry store, and a visual dashboard directly into a single library. graph TD Client(Browser/Client) -->|HTTP| Gateway subgraph User's Application Space Backend1[Backend Microservice A] Backend2[Backend Microserv
AI 资讯
Already using LaunchDarkly or Flagsmith? Here's how to try FtrIO on a single flag
Thinking about trying FtrIO? The new CLI makes it easy to start with just one feature flag If you've been curious about FtrIO (the .NET feature toggle library that replaces if (featureFlags.IsEnabled(...)) with a [Toggle] attribute woven directly into your compiled IL) but weren't sure where to start, the experimental release of FtrIO.onetwo just made that first step a lot smaller. You don't need to commit to a full migration. You don't need to rip out your existing flag library. You just need one method and 20 minutes. What FtrIO.onetwo does FtrIO.onetwo is a .NET CLI audit tool. Its default mode scans your source tree, finds every FtrIO toggle reference, and tells you exactly what's live right now: dotnet tool install --global FtrIO.onetwo --version 1.1.1-experimental ftrio.onetwo --source C: \P rojects \M yApp But in this experimental release it does two new things: ftrio.onetwo import : pulls your current flag state from LaunchDarkly, Flagsmith, flagd, environment variables, or an HTTP endpoint directly into appsettings.json . Your existing flag library keeps working unchanged. ftrio.onetwo migrate : scans your .cs files for LaunchDarkly or Flagsmith SDK call patterns using Roslyn, cross-references them against your live flag state, and generates a report showing exactly what each flag would look like in FtrIO and how to migrate it. The "try it on one flag" workflow The migrate report categorises every flag it finds: ✅ Ready to migrate : boolean flag, no targeting rules, straightforward [Toggle] replacement ⚠️ Needs review : targeting rules, number flags, needs a decision ❌ Cannot migrate : JSON flags, recommend moving to IConfiguration For every ready flag it shows the suggested refactor. Something like: new - checkout - flow → NewCheckoutFlow File : Services \ OrderService . cs : 42 Current code : if ( client . BoolVariation ( "new-checkout-flow" , user , false )) { ValidateCart (); ApplyDiscounts (); ProcessPayment (); } Suggested action : Extract the if bloc
AI 资讯
This week in Cursor + .NET — 7 rules (week ending June 21, 2026)
Every weekday a single, opinionated rule for senior C#/.NET engineers using Cursor. Here's the full week in one read — canonical posts live on the Agentic Architect blog . 7 daily senior rules Rule 14: Sealed By Default Sun 21 Jun Mark every class sealed unless inheritance is explicitly planned. Stops Cursor inventing accidental inheritance hierarchies "for flexibility." Small but measurable virtual-call perf wins too. → Permalink on the blog Rule 13: Strongly-Typed IDs Sat 20 Jun OrderId as record struct OrderId(Guid Value) beats raw Guid everywhere. Stops the AI passing a CustomerId where an OrderId was expected — a bug the compiler can't catch with primitive obsession but catches instantly with domain primitives. → Permalink on the blog Rule 12: IOptionsSnapshot Over Raw Config Fri 19 Jun Business code should never call IConfiguration directly. Strongly-typed IOptions or IOptionsSnapshot bindings only. The AI loves to "just grab the config value" — refuse it and force a settings class with validation attributes. → Permalink on the blog Rule 11: Rethrow, Don't throw ex Thu 18 Jun throw ex resets the stack trace. throw preserves it. Cursor gets this wrong about 40 percent of the time when generating catch blocks. Rewrite any naked throw ex to throw unless the exception has been explicitly wrapped. → Permalink on the blog Rule 10: AsNoTracking for Reads Wed 17 Jun Every read-only EF Core query should call AsNoTracking. Add a rule that recognises query methods returning DTOs (not entities) and inserts the call. Cursor never does this by default and your read perf degrades silently across releases. → Permalink on the blog Rule 9: Scoped Capture in Singleton Tue 16 Jun The single most expensive .NET runtime bug: a Singleton holding a Scoped service. Cursor cheerfully writes this without warning. Audit constructor parameters of any class registered as Singleton — if any are typically Scoped (DbContext, repositories, MediatR sender), flag it before merge. → Permalink on
AI 资讯
Generics in C# (List , Dictionary )
Originally published at https://allcoderthings.com/en/article/csharp-generics-list-t-dictionary-tkey-tvalue In C#, generics are used to increase type safety and flexibility. Generic classes and collections eliminate the need for runtime type casting and avoid unnecessary boxing and unboxing operations, improving performance and reducing the risk of errors. Before generics were introduced, collections such as ArrayList stored elements as object . When a value type like int was added to an ArrayList , it had to be boxed (converted to object ), and later unboxed when retrieved. This boxing/unboxing process caused additional memory allocations and performance overhead. With generic collections like List<T> and Dictionary<TKey,TValue> , elements are stored in their actual types, eliminating these costs and making the code both safer and faster. List List<T> is a generic collection that dynamically stores elements of a specific type. T specifies the type of elements the list will contain. using System ; using System.Collections.Generic ; var numbers = new List < int >(); numbers . Add ( 10 ); numbers . Add ( 20 ); numbers . Add ( 30 ); foreach ( int n in numbers ) Console . WriteLine ( n ); // Output: // 10 // 20 // 30 Note: Unlike arrays, List<T> can grow and shrink dynamically. Dictionary Dictionary is a generic key–value collection. TKey specifies the type of the key, and TValue specifies the type of the value. using System ; using System.Collections.Generic ; var students = new Dictionary < int , string >(); students [ 101 ] = "John" ; students [ 102 ] = "Mary" ; students [ 103 ] = "Michael" ; foreach ( var kv in students ) Console . WriteLine ( $" { kv . Key } → { kv . Value } " ); // Output: // 101 → John // 102 → Mary // 103 → Michael Note: Each Key in a dictionary must be unique. Attempting to add the same key again will cause an error. Creating Your Own Generic Classes You can also define your own generic types, not just use built-in collections. This allows you