Sr. Content Developer at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
146122 stories
·
33 followers

Force DbContext SaveChanges to throw exception during test

1 Share

I have the following code:

public async Task<Result<Guid>> HandleClient<T>(T client) where T : Client
{
    var exists = await _dbContext.Clients.AnyAsync(c => c.ClientId == client.ClientId);
    
    if(exists)
    {
        return Result.Failure<Guid>(new ConflictError(nameof(Client.ClientId), client.ClientId));
    }

    try
    {
        _dbContext.Clients.Add(client);
        await _dbContext.SaveChangesAsync();
        return Result.Success(client.Id);
    }
    catch(Exception e)
    {
        return Result.Failure<Guid>(
            new ExceptionError($"Error when trying to create client '{client.ClientId}'", e));
    }
}

I already have the following integration test that covers the "exists" case:

[Fact]
public async Task ShouldReturn409ConflictWhenClientIdIsNotUnique()
{
    var clientId = Guid.CreateVersion7().ToString();
    var existingClient = new PublicClientBuilder { ClientId = clientId }.Build();
    await using var arrangeScope = _fixture.Services.CreateAsyncScope();
    await using var arrangeDbContext = arrangeScope.ServiceProvider.GetRequiredService<MyDbContext>();
    arrangeDbContext.Clients.Add(existingClient);
    await arrangeDbContext.SaveChangesAsync(_ct);
    var requestBody = new CreatePublicClientRequestBodyBuilder { ClientId = clientId }.Build();
    var client = _fixture.CreateClient();
    var request = new HttpRequestMessage(HttpMethod.Post, "clients") { Content = new JsonContent(requestBody) };

    var response = await client.SendAsync(request, _ct);

    response.StatusCode.ShouldBe(HttpStatusCode.Conflict);
    var problemDetails = await response.ReadProblemDetails(_ct);
    problemDetails.Title.ShouldBe("Conflict");
    problemDetails.Detail.ShouldBe($"'{clientId}' already exists (ClientId)");
}

Now, I also want to test that my code catches exceptions and returns a Result.

My _fixture looks like this:

public class WebFixture : WebApplicationFactory<Program>, IAsyncLifetime
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder
            .UseEnvironment("IntegrationTest")
            .ConfigureAppConfiguration(configurationBuilder =>
            {
                configurationBuilder.Sources.Clear();
                configurationBuilder.AddInMemoryCollection(TestConfiguration!);
                configurationBuilder.AddEnvironmentVariables("TEST_");
            });
    }

    public async ValueTask InitializeAsync()
    {
        await using var scope = Services.CreateAsyncScope();
        await using var dbContext = scope.ServiceProvider.GetRequiredService<MyDbContext>();
        await dbContext.Database.MigrateAsync();
    }
}

I can then resolve MyDbContext like this in my tests:

await using var arrangeScope = _fixture.Services.CreateAsyncScope();
await using var arrangeDbContext = arrangeScope.ServiceProvider.GetRequiredService<MyDbContext>();

It's then possible to create a HttpClient like this and call my endpoint:

var client = _fixture.CreateClient();

Now I only needed a way to be able to configure MyDbContext to throw an exception when SaveChangesAsync is called.

The registration of MyDbContext looks like this:

public static void AddDatabase(this IServiceCollection services, IConfiguration configuration)
{
       services.Configure<PostgresDatabaseOptions>(configuration.GetSection("Storage:Postgres"));
services.AddSingleton<PostgresDatabaseOptions>(x =>
    x.GetRequiredService<IOptions<PostgresDatabaseOptions>>().Value);
services.AddSingleton<NpgsqlDataSource>(provider =>
{
    var postgresOptions = provider.GetRequiredService<PostgresDatabaseOptions>();
    var builder = new NpgsqlConnectionStringBuilder(postgresOptions.ConnectionString)
    {
        Pooling = true,
        MinPoolSize = 2,
        MaxPoolSize = 50
    };
    return NpgsqlDataSource.Create(builder.ConnectionString);
});
services.AddDbContext<MyDbContext, PostgresDbContext>((provider, options) =>
{
    var dataSource = provider.GetRequiredService<NpgsqlDataSource>();
    var interceptors = provider.GetServices<IInterceptor>();
    options.UseNpgsql(dataSource).UseSnakeCaseNamingConvention().AddInterceptors(interceptors.ToArray());
});
}

As you can see, I'm using Postgres and then I configure MyDbContext. When configuring MyDbContext, I also resolve all IInterceptor implementations from the container and ensure it's used.

var interceptors = provider.GetServices<IInterceptor>();
options.UseNpgsql(dataSource).UseSnakeCaseNamingConvention().AddInterceptors(interceptors.ToArray());

I've created the following interceptor in my test project:

public class ThrowingInterceptor : SaveChangesInterceptor
{
    public override ValueTask<InterceptionResult<int>> SavingChangesAsync(
        DbContextEventData eventData,
        InterceptionResult<int> result,
        CancellationToken cancellationToken = default)
    {
        throw new Exception("Boom from test");
    }
}

Now I only need a way to register my interceptor from my test to make sure that it's used.

I've created the following method in my WebFixture:

ublic HttpClient CreateClient(Action<IServiceCollection>? configureServices)
{
    if(configureServices is null)
    {
        return CreateClient();
    }

    return WithWebHostBuilder(builder =>
    {
        builder.ConfigureServices(configureServices);
    }).CreateClient();
}

Since it takes an Action<IServiceCollection>, it's possible to configure the IServiceCollection from the test like this:

var client = _fixture.CreateClient(services =>
{
    services.AddScoped<IInterceptor, ThrowingInterceptor>();
});

The following test will now pass:

[Fact]
    public async Task ShouldReturn500InternalServerErrorWhenExceptionHappens()
{
    var clientId = Guid.CreateVersion7().ToString();
    await using var arrangeScope = _fixture.Services.CreateAsyncScope();
    await using var arrangeDbContext = arrangeScope.ServiceProvider.GetRequiredService<AwthDbContext>();
    var requestBody = new CreatePublicClientRequestBodyBuilder { ClientId = clientId }.Build();
    var request = new HttpRequestMessage(HttpMethod.Post, "clients") { Content = new JsonContent(requestBody) };
    var client = _fixture.CreateClient(services =>
    {
        services.AddScoped<IInterceptor, ThrowingInterceptor>();
    });

    var response = await client.SendAsync(request, _ct);

    response.StatusCode.ShouldBe(HttpStatusCode.InternalServerError);
    var problemDetails = await response.ReadProblemDetails(_ct);
    // Generic response since my API should not leak exception messages
    problemDetails.Title.ShouldBe("An error occurred while processing your request.");
}
Read the whole story
alvinashcraft
10 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

BONUS Thinking Like an Architect in the Age of AI-Assisted Coding With Brian Childress

1 Share

BONUS: Thinking Like an Architect in the Age of AI-Assisted Coding

How can engineers leverage AI to write better code—and think like architects to build systems that truly scale? In this episode, Brian Childress, a CTO and software architect with over 15 years of experience, shares hard-won lessons from teams using AI coding tools daily, and explains why the real challenge isn't just writing code—it's designing systems that scale with users, features, and teams.

The Complexity Trap: When AI Multiplies Our Problems

"Most engineering projects and software engineers themselves lean more towards complexity, and I find that that complexity really is multiplied when we bring in the power of AI and its ability to write just tons and tons and tons of code."

 

Brian has observed a troubling pattern: AI tools can generate deeply nested components with complex data flows that technically work but are nearly impossible to understand or maintain. When teams don't guide AI through architectural decisions, they end up with code that becomes "a little too complex for us to understand what is actually going on here." The speed at which AI produces code makes understanding the underlying problem even more critical—we can solve problems quickly, but we must ensure we're solving them the right way.

In this segment, we mention our longer AI Assisted Coding podcast series. Check that out for further insights and different perspectives on how our software community is learning to make better use of AI Assisted Coding tools. 

Vibe Coding Has Its Place—But Know Its Limits

"Vibe coding is incredibly powerful for designers and product owners who want to prompt until they get something that really demonstrates what they're trying to do."

 

Brian sees value across the entire spectrum from vibe coding to architect-driven development. Vibe coding allows teams to move from wireframes and Figma prototypes to actual working code much faster, enabling quicker validation with real customers. The key distinction is knowing when to use each approach:

 

  • Vibe coding works well for rapid prototyping and testing whether something has value

  • Architect thinking becomes essential when building production systems that need to scale and be maintained

What Does "Thinking Like an Architect" Actually Mean?

"When I'm thinking more like an architect, I'm thinking more around how bigger components, higher level components start to fit together."

 

The architect mindset shifts focus from "how do I work within a framework" to "what is the problem I'm really solving?" Brian emphasizes that technology is actually the easiest part of what engineers do—you can Google or AI your way to a solution. The harder work is ensuring that the solution addresses the real customer need. An architect asks: How can I simplify? How can I explain this to someone else, technical or non-technical? The better you can explain it, the better you understand it.

AI as Your Thought Partner

"What it really forces us to do is to be able to explain ourselves better. I find most software engineers will hide behind complexity because they don't understand the problem."

 

Brian uses AI as a collaborative thought partner rather than just a code generator. He explains the problem, shares his thought process, and then strategizes back and forth—looking for questions that challenge his thinking. This approach forces engineers to communicate clearly instead of hiding behind technical jargon. The AI becomes like having a colleague with an enormous corpus of knowledge who can see solutions you might never have encountered in your career.

Simplicity Through Four Shapes

"I basically use four shapes to be able to diagram anything, and if I can't do that, then we still have too much complexity. It's a square, a triangle, a circle, and a line."

 

When helping colleagues shift from code-writing to architect-thinking, Brian insists on dead simplicity. If you can diagram a system—from customer-facing problems down to code component breakdowns, data flow, and integrations—using only these four basic shapes, you've reached true understanding. This simplification creates that "light bulb moment" where engineers suddenly get it and can translate understanding into code while in flow state.

Making AI Work Culturally: Leading by Example

"For me as a leader, as a CTO, I need to show my team this is how I'm using it, this is where I'm messing up with it, showing that it's okay."

 

Brian addresses the cultural challenge head-on: mid-level and senior engineers often resist AI tools, fearing job displacement or having to support "AI slop." His approach is to frame AI as a new tool to learn—just like Google and Stack Overflow were in years past—rather than a threat. He openly shares his experiments, including failures, demonstrating that it's acceptable to laugh at garbage code while learning from how it was generated.

The Guardrails That Make AI Safe

"If we have all of that—the guardrails, the ability to test, automation—then AI just helps us to create the code in the right way, following our coding standards."

 

The same engineering practices that protect against human errors protect against AI mistakes: automated testing, deployment guardrails, coding standards, and code review. Brian sees an opportunity for AI to help teams finally accomplish what they've always wanted but never had time for—comprehensive documentation and thorough automated test suites.

Looking Ahead: More Architects, More Experiments, More Failures

"I'm going to see more engineers acting like architects, more engineers thinking in ways of how do I construct this system, how do I move data around, how do I scale."

 

Brian's 2-3 year prediction: engineers will increasingly think architecturally because AI removes the need to deeply understand framework nuances. We'll have more time for safeguards, automated testing, and documentation. But expect both sides of the spectrum to intensify—more engineers embracing AI tools, and more resistance and high-profile failures from CEOs vibe-coding production apps into security incidents.

Resources for Learning

Brian recommends staying current through YouTube channels focused on AI and developer tools. His top recommendations for developer-focused AI content:

 

 

His broader advice: experiment with everything, document what you learn as you go, and be willing to fail publicly. The engineers who thrive will be those actively experimenting and learning.

 

About Brian Childress

 

Brian Childress is a CTO and software architect with over 15 years of experience working across highly regulated industries including healthcare, finance, and consumer SaaS products. He brings a non-traditional background to technology leadership, having built his expertise through dedication and continuous learning rather than formal computer science education. Brian is passionate about helping engineers think architecturally and leverage AI tools effectively while maintaining simplicity in system design.

 

You can link with Brian Childress on LinkedIn.





Download audio: https://traffic.libsyn.com/secure/scrummastertoolbox/20260124_Brian_Childress_BONUS.mp3?dest-id=246429
Read the whole story
alvinashcraft
10 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

Pro Tip for managing 10 Coding Agents in the Windows Terminal

1 Share
From: Scott Hanselman
Duration: 1:46
Views: 535

Read the whole story
alvinashcraft
11 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

Hands On: Testing Cursor, Windsurf and VS Code on Text-to-Website Generation

1 Share
A hands-on comparison shows how Cursor, Windsurf, and Visual Studio Code approach text-to-website generation differently once they move beyond the basics and begin redesigning and extending their own work.
Read the whole story
alvinashcraft
11 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

Microsoft Agent Framework: Using Background Responses to Create an AI Researcher and Newsletter Publisher

1 Share

Keeping up with AI news can be a challenge.

Every day there’s a new model release, a new framework update, or some announcement that changes the landscape.

We find ourselves checking feeds, GitHub repositories, and blogs.  It’s a bit of an effort.

 What if we could build an AI agent that does this research for us?

 An agent that scans multiple sources, aggregates the important stuff you interested in, and generates a polished newsletter we can read?

In this blog post, we’ll build exactly that using the Microsoft Agent Framework and the Background Responses feature.

We cover the following:

  • What Are Background Responses
  • Why Use Background Responses
  • The Basics of Background Responses
  • Creating an AI Researcher and Newsletter Publisher
  • Core Features of the AINewsServiceAgent
  • AINewsServiceAgent Process, Prompts, and Function Tools
  • Bringing It All Together
  • Calling the AINewsServiceAgent from a Console Application
  • Implementing Background Responses for the AINewsServiceAgent

 

Our AI agent will search multiple news sources, monitor GitHub repositories for code updates, and generate a formatted markdown newsletter.  All while keeping the UI responsive with a nice progress indicator.

A video demo and source code are included.

~

What Are Background Responses

When working with AI agents that need to perform multiple tool calls such as, searching news sources, fetching GitHub commits, or generating a newsletter, processing can take a while.

Traditional synchronous execution means your application sits there waiting, potentially timing out or leaving users staring at a frozen screen.

Background Responses solve this problem by allowing the agent to process requests asynchronously.

Instead of blocking until completion, the agent returns a continuation token that your application can use to poll for progress.

This is like how you might check on a long-running job in a queue-based system.

The OpenAIResponseClient makes this possible.  Standard chat clients like GetChatClient run synchronously and do not support background processing.

Learn more about this here.

~

Why Use Background Responses

There are several reasons to use background responses for agent-based solutions.

  • Non-blocking execution allows your application to remain responsive while the agent works. Crucial for long-running tasks that involve multiple tool calls, API requests, or complex reasoning
  • Progress indication becomes possible because you can update the UI between polling intervals. Users can see that something is happening rather than wondering if the application has crashed.
  • Timeout prevention Long-running synchronous requests can hit HTTP timeout limits. With background responses, each poll is a quick check rather than a sustained connection

 

In general, users appreciate knowing an operation is in progress and roughly how long it’s been running.  Your agent can then report back to you via a communication channel of your choice when an operation has completed.

~

The Basics of Background Responses

The implementation pattern is straightforward. First, you create the agent using the OpenAI Responses client rather than the chat client.

#pragma warning disable OPENAI001 // OpenAI Responses API is in preview
var responseClient = new OpenAIClient(apiKey)
    .GetOpenAIResponseClient(model);


AIAgent agent = responseClient.CreateAIAgent(
    name: "AI News Digest Agent",
    instructions: "...",
    tools: [/* your tools */]);

 

Next, you enable background responses in the options:

AgentRunOptions options = new() { AllowBackgroundResponses = true };

 

Finally, you poll using the continuation token until the agent completes:

AgentRunResponse response = await agent.RunAsync(input, thread, options);

while (response.ContinuationToken is { } token)
{
    await Task.Delay(TimeSpan.FromMilliseconds(200));

    options.ContinuationToken = token;
    response = await agent.RunAsync(thread, options);
}


// Agent has completed - ContinuationToken is null
Console.WriteLine($"Agent: {response.Text}");
options.ContinuationToken = null;  // Reset for next request

The code within the  while loop contains the continuation token.

~

Content Overload. More Signal. Less Noise.

The AI space moves incredibly fast.   Microsoft updates the Agent Framework.  Google announces new Gemini features  Anthropic ships Claude improvements.  Open AI releases and more.

Something every other week.\

If you’re a developer trying to stay current, you’re probably:

  • Checking your trusted resources
  • Watching GitHub repositories for new releases and commits
  • Scanning tech news sites for announcements
  • Trying to figure out what’s actually important versus what’s just noise

 

What we need is a personal AI research assistant that:

  1. Aggregates news from trusted sources
  2. Filters by relevance to specific companies or topics
  3. Tracks code repositories for meaningful updates
  4. Generates a readable digest we can review in minutes, not hours

 

In the next section, that’s exactly what we’ll build.

~

Creating an AI Researcher and Newsletter Publisher

Our solution consists of three main components:

  1. AINewsServiceAgent – The function tools that search various sources and generate the newsletter
  2. NewsItemModel – A simple data model for news items
  3. Program.cs – The console application that orchestrates the agent with background responses

 

Let’s walk through each component.

~

Core Features of the AINewsServiceAgent

The AINewsServiceAgent class provides four function tools that the AI agent can call:

Tool Description
SearchFoundryBlog() Fetches the latest Microsoft AI Foundry blog posts
SearchAgentFrameworkUpdates() Gets recent GitHub commits and releases from the Agent Framework repository
SearchCompanyNews(company) Searches RSS feeds for company-specific AI news
GenerateNewsletter(title, content) Saves the compiled newsletter to a markdown file

 

The agent also exposes a status property that the console application can display during polling:

public static string CurrentStatus { get; private set; } = "Starting...";
public static void ResetStatus() => CurrentStatus = "Starting...";

~

AINewsServiceAgent Process

Here’s the flow when a user requests a newsletter digest:

Newsletter saved to: ai_digest_2026-01-24.md

Each tool call updates the CurrentStatus property.  The console application displays this and keeps the user informed of what is happening.

~

AINewsServiceAgent Prompt

The agent’s instructions are crucial. They define the workflow, newsletter format, and quality expectations.

Here’s the complete prompt:

instructions: """

You are an AI news research agent that creates weekly digest newsletters.


Workflow:

Call SearchFoundryBlog() to get Microsoft AI Foundry updates
Call SearchAgentFrameworkUpdates() to get Microsoft Agent Framework code updates
Call SearchCompanyNews for each company: Microsoft, Google, Anthropic, OpenAI
Generate a markdown newsletter using GenerateNewsletter with ALL results




CRITICAL: When generating the newsletter content for GenerateNewsletter:

- You MUST include ALL headlines returned from ALL search functions

- For each news item, include: Headline, PublishedDate (if available), Summary, and Url

- IMPORTANT: Each headline MUST be paired with its MATCHING Url, Summary, and PublishedDate

- Do NOT mix up links - the Url for "Article A" must only appear with "Article A"

- Format URLs as markdown links: [Read more](Url)

- ALWAYS include the PublishedDate field when it is not empty

- Do NOT skip or omit any results - include everything returned



Newsletter format (use these exact section headers):

Brief executive summary (2-3 sentences about key themes)
"## Microsoft AI Foundry" section - ALL results from SearchFoundryBlog
"## Microsoft Agent Framework" section - ALL results from SearchAgentFrameworkUpdates
"## Microsoft" section - results from SearchCompanyNews("Microsoft")
"## Google" section - results from SearchCompanyNews("Google")
"## Anthropic" section - results from SearchCompanyNews("Anthropic")
"## OpenAI" section - results from SearchCompanyNews("OpenAI")
"## What to Watch" section with emerging trends
"## Predictions for the Next 90 Days" section - list 3-5 specific predictions
with confidence scores (Low/Medium/High)



Each story format:

**Headline** *(Jan 15, 2026)* - Summary [Read more](url)


Style:

- Professional, concise
- Include ALL source URLs as markdown links
- Do not invent or fabricate news - only use results from the search functions
- MUST have a blank line between each news item for proper formatting

 

A few things to note about this prompt:

  • Explicit workflow ensures the agent calls tools in a predictable order
  • CRITICAL sections prevent common LLM issues like mixing up URLs or skipping results
  • Structured format produces consistent, readable newsletters
  • Predictions with confidence adds analytical value beyond simple aggregation

~

AINewsServiceAgent Models

The NewsItemModel class is simple and captures essential fields for a news item:

public class NewsItemModel
{
    public string Headline { get; set; } = string.Empty;
    public string Summary { get; set; } = string.Empty;
    public string Url { get; set; } = string.Empty;
    public string Title { get; set; } = string.Empty;
    public string Description { get; set; } = string.Empty;
    public string Source { get; set; } = string.Empty;
    public string Company { get; set; } = string.Empty;
    public string PublishedDate { get; set; } = string.Empty;

    public NewsItemModel(string Headline, string Summary, string Url,
                         string Source, string Company, string PublishedDate = "")
    {
        this.Headline = Headline;
        this.Summary = Summary;
        this.Url = Url;
        this.Source = Source;
        this.Company = Company;
        this.PublishedDate = PublishedDate;
    }
}

 

The PublishedDate is stored as a formatted string (e.g., “Jan 15, 2026”) rather than a DateTime.

This simplifies the agent’s job when generating the newsletter – it can use the date string directly without formatting concerns.

~

AINewsServiceAgent Function Tools

Let’s examine each function tool in detail.  When you supply a prompt, the AI agent automatically decides which of these to run.

SearchCompanyNews

This tool searches multiple RSS feeds for news about a specific company:

[Description("Searches for recent AI news and developments from a specific company.")]
public static async Task<List<NewsItemModel>> SearchCompanyNews(
    [Description("The company to search for: Microsoft, Google, Anthropic, or OpenAI")]
    string company)
{
    CurrentStatus = $"Searching {company} news...";
    var results = new List<NewsItemModel>();

    var keywords = company.ToLower() switch
    {
        "microsoft" => new[] { "microsoft", "copilot", "azure", "bing" },
        "google" => new[] { "google", "gemini", "deepmind", "bard" },
        "anthropic" => new[] { "anthropic", "claude" },
        "openai" => new[] { "openai", "chatgpt", "gpt-4", "gpt-5", "dall-e", "sora" },
        _ => new[] { company.ToLower(), "ai", "artificial intelligence" }
   };


    foreach (var feedUrl in _rssFeeds)
    {
        var feedContent = await _httpClient.GetStringAsync(feedUrl);
        var feedResults = ParseRssFeed(feedContent, company, keywords, feedUrl);
        results.AddRange(feedResults);
   }

    return results.Take(5).ToList();
}

The keyword mapping ensures we catch related terms. For example, searching for Microsoft also finds articles about Copilot and Azure.

SearchFoundryBlog

This tool scrapes the Microsoft Foundry DevBlog for the latest posts:

[Description("Searches the Microsoft Foundry blog for the latest AI Foundry updates.")]
public static async Task<List<NewsItemModel>> SearchFoundryBlog()
{
    CurrentStatus = "Fetching Microsoft Foundry blog...";
    var results = new List<NewsItemModel>();

    var htmlContent = await _httpClient.GetStringAsync(FoundryBlogUrl);
    var keywords = new[] { "" }; // Match all articles - Foundry is all AI-related
    results = ParseBlogPage(htmlContent, "Microsoft", keywords, FoundryBlogUrl);

    return results.Take(10).ToList();
}

 

Since the Foundry blog is entirely AI-focused, we don’t filter by keywords – every article is relevant.

SearchAgentFrameworkUpdates

This tool fetches recent commits and releases from the Agent Framework GitHub repository:

[Description("Searches the Microsoft Agent Framework GitHub repository for recent code updates.")]
public static async Task<List<NewsItemModel>> SearchAgentFrameworkUpdates()
{
    CurrentStatus = "Fetching GitHub Agent Framework updates...";
    return await FetchGitHubUpdates("microsoft", "agent-framework", "Microsoft");
}

 

The FetchGitHubUpdates helper method queries the GitHub API for commits from the last 7 days, focusing on significant changes like features, examples, and releases.

GenerateNewsletter

This tool saves the compiled newsletter to a markdown file:

[Description("Generates a markdown newsletter digest and saves it to file.")]
public static string GenerateNewsletter( [Description("The newsletter title")] string title, [Description("The full markdown content of the newsletter")] string markdownContent)
{
    CurrentStatus = "Generating newsletter...";
    var filename = $"ai_digest_{DateTime.Now:yyyy-MM-dd}.md";

    var header = $"""
               # {title}
               **Generated:** {DateTime.Now:dddd, MMMM d, yyyy 'at' h:mm tt}
               ---
               """;


   var fullContent = header + markdownContent;
   File.WriteAllText(filename, fullContent);

   SaveLastRunTime();

   return $"Newsletter saved to {filename}";
}

~

AINewsServiceAgent Helper Methods

Several helper methods support the function tools.

ParseRssFeed

This method handles both RSS 2.0 and Atom feed formats:

private static List<NewsItemModel> ParseRssFeed(string feedContent, string company, string[] keywords, string feedUrl)
{
    var results = new List<NewsItemModel>();
    var doc = XDocument.Parse(feedContent);
    XNamespace atom = "http://www.w3.org/2005/Atom";

    // Handle both RSS 2.0 and Atom feeds
    var items = doc.Descendants("item").ToList();
    var isAtomFeed = false;
    if (!items.Any())
    {
        items = doc.Descendants(atom + "entry").ToList();
        isAtomFeed = true;
    }

    foreach (var item in items)
    {
        var title = GetElementValue(item, "title") ?? "";
        var description = GetElementValue(item, "description")
                        ?? GetElementValue(item, "summary")
                        ?? GetElementValue(item, "content")
                        ?? "";

        // Extract link - different for Atom vs RSS
        var link = isAtomFeed
            ? item.Elements(atom + "link")
                  .FirstOrDefault(e => e.Attribute("rel")?.Value == "alternate")
                  ?.Attribute("href")?.Value ?? ""
            : GetElementValue(item, "link") ?? "";

        var pubDateStr = GetElementValue(item, "pubDate")
                       ?? GetElementValue(item, "published")
                       ?? GetElementValue(item, "updated")
                       ?? "";
 
        var publishedDate = FormatPublishedDate(pubDateStr);
        var searchText = $"{title} {description}".ToLower();
        if (keywords.Any(k => searchText.Contains(k)))
        {
            // Clean up and add to results
            description = Regex.Replace(description, "<[^>]+>", "");
            if (description.Length > 300)

               description = description[..300] + "...";
               results.Add(new NewsItemModel(
                Headline: title,
                Summary: description,
                Source: GetSourceName(feedUrl),
                Url: link,
                Company: company,
                PublishedDate: publishedDate
            ));
       }
    }
    return results;
}

FetchGitHubUpdates

This method queries the GitHub API for recent repository activity:

private static async Task<List<NewsItemModel>> FetchGitHubUpdates(
string owner, string repo, string company)
{
var results = new List<NewsItemModel>();
var since = DateTime.UtcNow.AddDays(-7);


using var request = new HttpRequestMessage(HttpMethod.Get,
$"https://api.github.com/repos/{owner}/{repo}/commits?since={since:yyyy-MM-ddTHH:mm:ssZ}&per_page=20");
request.Headers.Add("User-Agent", "AI-News-Agent");
request.Headers.Add("Accept", "application/vnd.github.v3+json");

if (!string.IsNullOrEmpty(_githubToken))
request.Headers.Add("Authorization", $"Bearer {_githubToken}");


var response = await _httpClient.SendAsync(request);
if (!response.IsSuccessStatusCode) return results;

var commits = await response.Content.ReadFromJsonAsync<JsonElement>();

foreach (var commit in commits.EnumerateArray())
{
var message = commit.GetProperty("commit")
.GetProperty("message").GetString() ?? "";
var firstLine = message.Split('\n')[0];
var url = commit.GetProperty("html_url").GetString() ?? "";


// Filter for significant commits
var lowerMessage = firstLine.ToLower();

if (lowerMessage.Contains("feat") || lowerMessage.Contains("add") ||
lowerMessage.Contains("new") || lowerMessage.Contains("example"))
{
results.Add(new NewsItemModel(
Headline: $"[Code] {firstLine}",
Summary: $"Update in {owner}/{repo}",
Source: "GitHub",
Url: url,
Company: company,
PublishedDate: FormatPublishedDate(dateStr)
));
}
}


// Also check for releases
await FetchGitHubReleases(owner, repo, company, since, results);

return results;
}

 

The method filters commits to focus on features, additions, and examples rather than noise like documentation typos or merge commits.

~

Bringing It All Together

The Program.cs file wires everything up. Here’s the main structure:

static async Task Main(string[] args)
{
    // Create the OpenAI Responses client (supports background responses)
    var responseClient = new OpenAIClient(apiKey)
        .GetOpenAIResponseClient(model);

    AIAgent agent = responseClient.CreateAIAgent(
        name: "AI News Digest Agent",
        instructions: """
  You are an AI news research agent that creates weekly digest newsletters.

  Workflow:
  1. Call SearchFoundryBlog() to get Microsoft AI Foundry updates
  2. Call SearchAgentFrameworkUpdates() to get Microsoft Agent Framework code updates
  3. Call SearchCompanyNews for each company: Microsoft, Google, Anthropic, OpenAI
  4. Generate a markdown newsletter using GenerateNewsletter with ALL results

                      CRITICAL: When generating the newsletter content for GenerateNewsletter:
                      - You MUST include ALL headlines returned from ALL search functions
                      - For each news item, include: Headline, PublishedDate (if available), Summary, and Url
                      - IMPORTANT: Each headline MUST be paired with its MATCHING Url, Summary, and PublishedDate from the same result
                      - Do NOT mix up links - the Url for "Article A" must only appear with "Article A"
                      - Format URLs as markdown links: [Read more](Url)
                      - ALWAYS include the PublishedDate field when it is not empty
                      - Do NOT skip or omit any results - include everything returned

Newsletter format (use these exact section headers):
   1. Brief executive summary (2-3 sentences about key themes)
   2. "## Microsoft AI Foundry" section - ALL results from SearchFoundryBlog
   3. "## Microsoft Agent Framework" section - ALL results from SearchAgentFrameworkUpdates. Only C# and .NET. Do not include Python code.
   4. "## Microsoft" section - results from SearchCompanyNews("Microsoft")
   5. "## Google" section - results from SearchCompanyNews("Google")
   6. "## Anthropic" section - results from SearchCompanyNews("Anthropic")
   7. "## OpenAI" section - results from SearchCompanyNews("OpenAI")
   8. "## What to Watch" section with emerging trends
   9. "## Predictions for the Next 90 Days" section - REQUIRED: Based on the news gathered, list 3-5 specific predictions about what might happen in AI over the next 90 days. For EACH prediction, include a confidence score (Low/Medium/High) based on the strength of evidence from the news.

Format each prediction as:

- **Prediction:** [Your prediction] **Confidence:** [Low/Medium/High] - [Brief reasoning for the confidence level]

              Each story format (IMPORTANT - include blank line after each item):

               WITH DATE (when PublishedDate field is not empty):
               **Headline** *(Jan 15, 2026)* - Summary [Read more](url)

               WITHOUT DATE (when PublishedDate field is empty):
               **Headline** - Summary [Read more](url)

               RULES:
               - Check each item's PublishedDate field - if it has a value like "Jan 15, 2026", include it in italics
         - The blank line between items is CRITICAL for proper markdown rendering
         - Copy the PublishedDate exactly as provided in the search results

   Style:
   - Professional, concise
   - Include ALL source URLs as markdown links
   - Do not invent or fabricate news - only use results from the search functions
                      - MUST have a blank line between each news item for proper formatting
                      """,
        tools: [
            AIFunctionFactory.Create(AINewsServiceAgent.SearchFoundryBlog),
            AIFunctionFactory.Create(AINewsServiceAgent.SearchAgentFrameworkUpdates),
            AIFunctionFactory.Create(AINewsServiceAgent.SearchCompanyNews),
            AIFunctionFactory.Create(AINewsServiceAgent.GenerateNewsletter)
        ]);

    AgentRunOptions options = new() { AllowBackgroundResponses = true };
    AgentThread thread = agent.GetNewThread();

    Console.WriteLine("=== AI News Digest Agent ===");
    Console.WriteLine("Commands:");
    Console.WriteLine("  'Generate this week's digest'");
    Console.WriteLine("  'What's new from Anthropic?'");
    Console.WriteLine("  'Create a newsletter focused on AI agents'");
    Console.WriteLine();

    while (true)
    {
        Console.Write("You: ");
        string? input = Console.ReadLine();
        if (string.IsNullOrEmpty(input)) break;

        var stopwatch = Stopwatch.StartNew();
        _progressPosition = 0;
        AINewsServiceAgent.ResetStatus();

        // Initial call - may return with continuation token if still processing
        AgentRunResponse response = await agent.RunAsync(input, thread, options);

        // Poll with continuation token until complete (framework's background responses pattern)
        while (response.ContinuationToken is { } token)
        {
            UpdateProgressBar(stopwatch);

            await Task.Delay(TimeSpan.FromMilliseconds(200));  // Poll every 1 seconds

            options.ContinuationToken = token;
            response = await agent.RunAsync(thread, options);
        }

        stopwatch.Stop();

        ClearProgressBar();

        Console.ForegroundColor = ConsoleColor.Green;
        Console.WriteLine($"Completed in {stopwatch.Elapsed:mm\\:ss}");
        Console.ResetColor();
        Console.WriteLine();
        Console.WriteLine($"Agent: {response.Text}");
        Console.WriteLine();

        options.ContinuationToken = null;
    }
}

You’ll see in the above code there is a method UpdateProgressBar method.  Lets look at that in more detail.

~

Implementing a Progress bar for the AINewsServiceAgent

The key to a good user experience is the progress bar. Here’s how it’s implemented:

private static int _progressPosition = 0;
private static readonly int _progressWidth = 30;

private static void UpdateProgressBar(Stopwatch stopwatch)
{
// Create animated progress bar
_progressPosition = (_progressPosition + 1) % (_progressWidth * 2);
var barPosition = _progressPosition < _progressWidth
? _progressPosition
: (_progressWidth * 2) - _progressPosition;


var bar = new string(' ', _progressWidth);
var barChars = bar.ToCharArray();


// Create a 3-character moving block
for (int i = 0; i < 3; i++)
{
var pos = (barPosition + i) % _progressWidth;
if (pos < _progressWidth) barChars[pos] = '█';
}


// Get current status from agent
var status = AINewsServiceAgent.CurrentStatus;
if (status.Length > 45) status = status[..42] + "...";


Console.ForegroundColor = ConsoleColor.Cyan;

Console.Write($"\rResearching [{new string(barChars)}] {stopwatch.Elapsed:mm\\:ss}");

Console.ResetColor();

Console.Write($"\n\r{status,-50}");

Console.SetCursorPosition(0, Console.CursorTop - 1);
}

 

This creates an animated block that bounces back and forth while displaying:

  • Elapsed time in mm:ss format
  • Current operation status (e.g., “Searching Microsoft news…”)

 

The result looks like this during execution:

Perfect.

~

Demo

Here’s a video showing the AI need agent in action:

TODO – record video

 

When complete, the output looks like:

Completed in 01:23

Agent: I've generated this week's AI news digest. The newsletter has been saved to

ai_digest_2026-01-21.md and includes updates from Microsoft AI Foundry, the Agent

Framework repository, and general AI news from Microsoft, Google, Anthropic, and OpenAI.




Key highlights this week:

- Microsoft released new Agent Framework features for memory persistence
- Google announced Gemini 2.0 improvements
- Anthropic shipped Claude performance updates
- OpenAI continued GPT-5 development

The newsletter includes predictions for the next 90 days with confidence scores.

The generated newsletter is a well-formatted markdown file with all sections, links, and dates properly attributed.

~

Summary

In this post, we built an AI-powered research agent that:

  • Uses Background Responses to handle long-running multi-tool workflows
  • Aggregates news from RSS feeds, blog pages, and GitHub repositories
  • Generates formatted newsletters with proper attribution and links
  • Provides real-time progress indication during processing
  • Includes predictions with confidence scores for added analytical value

 

You can adapt this pattern for any agent-based application where tool calls might take significant time.

This might be – document processing, data analysis, complex reasoning tasks, or any workflow involving multiple external API calls.

~

Further Reading

~

Enjoy what you’ve read, have questions about this content, or would like to see another topic?

Drop me a note below.

You can schedule a call using my Calendly link to discuss consulting and development services.

JOIN MY EXCLUSIVE EMAIL LIST
Get the latest content and code from the blog posts!
I respect your privacy. No spam. Ever.
Read the whole story
alvinashcraft
11 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

How to Use the Singleton Design Pattern in Flutter: Lazy, Eager, and Factory Variations

1 Share

In software engineering, sometimes you need only one instance of a class across your entire application. Creating multiple instances in such cases can lead to inconsistent behavior, wasted memory, or resource conflicts.

The Singleton Design Pattern is a creational design pattern that solves this problem by ensuring that a class has exactly one instance and provides a global point of access to it.

This pattern is widely used in mobile apps, backend systems, and Flutter applications for managing shared resources such as:

  • Database connections

  • API clients

  • Logging services

  • Application configuration

  • Security checks during app bootstrap

In this article, we'll explore what the Singleton pattern is, how to implement it in Flutter/Dart, its variations (eager, lazy, and factory), and physical examples. By the end, you'll understand the proper way to use this pattern effectively and avoid common pitfalls.

Table of Contents

  1. Prerequisites

  2. What is the Singleton Pattern?

  3. How to Create a Singleton Class

  4. Factory Constructors in the Singleton Pattern

  5. When Not to Use a Singleton

  6. Conclusion

Prerequisites

Before diving into this tutorial, you should have:

  1. Basic understanding of the Dart programming language

  2. Familiarity with Object-Oriented Programming (OOP) concepts, particularly classes and constructors

  3. Basic knowledge of Flutter development (helpful but not required)

  4. Understanding of static variables and methods in Dart

  5. Familiarity with the concept of class instantiation

What is the Singleton Pattern?

The Singleton pattern is a creational design pattern that ensures a class has only one instance and that there is a global point of access to the instance.

Again, this is especially powerful when managing shared resources across an application.

When to Use the Singleton Pattern

You should use a Singleton when you are designing parts of your system that must exist once, such as:

  1. Global app state (user session, auth token, app config)

  2. Shared services (logger, API client, database connection)

  3. Resource heavy logic (encryption handlers, ML models, cache manager)

  4. Application boot security (run platform-specific root/jailbreak checks)

For example, in a Flutter app, Android may check developer mode or root status, while iOS checks jailbroken device state. A Singleton security class is a perfect way to enforce that these checks run once globally during app startup.

How to Create a Singleton Class

We have two major ways of creating a singleton class:

  1. Eager Instantiation

  2. Lazy Instantiation

Eager Singleton

This is where the Singleton is created at load time, whether it's used or not.

In this case, the instance of the singleton class as well as any initialization logic runs at load time, regardless of when this class is actually needed or used. Here's how it works:

class EagerSingleton {
  EagerSingleton._internal();
  static final EagerSingleton _instance = EagerSingleton._internal();

  static EagerSingleton get instance => _instance;

  void sayHello() => print("Hello from Eager Singleton");
}

//usage
void main() {
  // Accessing the singleton globally
  EagerSingleton.instance.sayHello();
}

How the Eager Singleton Works

Let's break down what's happening in this implementation:

First, EagerSingleton._internal() is a private named constructor (notice the underscore prefix). This prevents external code from creating new instances using EagerSingleton(). The only way to get an instance is through the controlled mechanism we're about to define.

Next, static final EagerSingleton _instance = EagerSingleton._internal(); is the key line. This creates the single instance immediately when the class is first loaded into memory. Because it's static final, it belongs to the class itself (not any particular instance) and can only be assigned once. The instance is created right here, at declaration time.

The static EagerSingleton get instance => _instance; getter provides global access to that single instance. Whenever you call EagerSingleton.instance anywhere in your code, you're getting the exact same object that was created when the class loaded.

Finally, sayHello() is just a regular method to demonstrate that the singleton works. You could replace this with any business logic your singleton needs to perform.

When you run the code in main(), the class loads, the instance is created immediately, and EagerSingleton.instance.sayHello() accesses that pre-created instance to call the method.

Pros:

  1. This is simple and thread safe, meaning it's not affected by concurrency, especially when your app runs on multithreads.

  2. It's ideal if the instance is lightweight and may be accessed frequently.

Cons:

  1. If this instance is never used through the runtime, it results in wasted memory and could impact application performance.

Lazy Singleton

In this case, the singleton instance is only created when the class is called or needed in runtime. Here, a trigger needs to happen before the instance is created. Let's see an example:

class LazySingleton {
  LazySingleton._internal(); 
  static LazySingleton? _instance;

  static LazySingleton get instance {
    _instance ??= LazySingleton._internal();
    return _instance!;
  }

  void sayHello() => print("Hello from LazySingleton");
}

//usage 
void main() {
  // Accessing the singleton globally
  LazySingleton.instance.sayHello();
}

How the Lazy Singleton Works

The lazy implementation differs from eager in one crucial way: timing.

Again, LazySingleton._internal() is a private constructor that prevents external instantiation.

But notice that static LazySingleton? _instance; is declared as nullable and not initialized. Unlike the eager version, no instance is created at load time. The variable simply exists as null until it's needed.

The magic happens in the getter: _instance ??= LazySingleton._internal(); uses Dart's null-aware assignment operator. This line says "if _instance is null, create a new instance and assign it. Otherwise, keep the existing one." This is the lazy initialization: the instance is only created the first time someone accesses it.

The first time you call LazySingleton.instance, _instance is null, so a new instance is created. Every subsequent call finds that _instance already exists, so it just returns that same instance.

The return _instance!; uses the null assertion operator because we know _instance will never be null at this point (we just ensured it's not null in the previous line).

This approach saves memory because if you never call LazySingleton.instance in your app, the instance never gets created.

Pros:

  1. Saves application memory, as it only creates what is needed in runtime.

  2. Avoids memory leaks.

  3. Is ideal for resource heavy objects while considering application performance.

Cons:

  1. Could be difficult to manage in multithreaded environments, as you have to ensure thread safety while following this pattern.

Choosing Between Eager and Lazy

Now that we've broken down these two major types of singleton instantiation, it's worthy of note that you'll need to be intentional while deciding whether to create a singleton the eager or lazy way. Your use case/context should help you determine what singleton pattern you need to apply during object creation.

As an engineer, you need to ask yourself these questions when using a singleton for object creation:

  1. Do I need this class instantiated when the app loads?

  2. Based on the user journey, will this class always be needed during every session?

  3. Can a user journey be completed without needing to call any logic in this class?

These three questions will determine what pattern (eager or lazy) you should use to fulfill best practices while maintaining scalability and high performance in your application.

Factory Constructors in the Singleton Pattern

Applying factory constructors in the Singleton pattern can be powerful if you use them properly. But first, let's understand what factory constructors are.

What Are Factory Constructors?

A factory constructor in Dart is a special type of constructor that doesn't always create a new instance of its class. Unlike regular constructors that must return a new instance, factory constructors can:

  1. Return an existing instance (perfect for singletons)

  2. Return a subclass instance

  3. Apply logic before deciding what to return

  4. Perform validation or initialization before returning an object

The factory keyword tells Dart that this constructor has the flexibility to return any instance of the class (or its subtypes), not necessarily a fresh one.

Implementing Singleton with Factory Constructor

This allows you to apply initialization logic while your class instance is being created before returning the instance.

class FactoryLazySingleton {
  FactoryLazySingleton._internal();
  static final FactoryLazySingleton _instance = FactoryLazySingleton._internal();

  static FactoryLazySingleton get instance => _instance;

  factory FactoryLazySingleton() {
    // Your logic runs here
    print("Factory constructor called");
    return _instance;
  }
}

How the Factory Constructor Singleton Works

This implementation combines aspects of both eager and lazy patterns with additional control.

The FactoryLazySingleton._internal() private constructor and static final _instance create an eager singleton. The instance is created immediately when the class loads.

The static get instance provides the traditional singleton access pattern we've seen before.

But the interesting part is the factory FactoryLazySingleton() constructor. This is a public constructor that looks like a normal constructor call, but behaves differently. When you call FactoryLazySingleton(), instead of creating a new instance, it runs whatever logic you've placed inside (in this case, a print statement), then returns the existing _instance.

This pattern is powerful because:

  1. You can log when someone tries to create an instance

  2. You can validate conditions before returning the instance

  3. You can apply configuration based on parameters passed to the factory

  4. You can choose to return different singleton instances based on conditions

For example, you might have different configuration singletons for development vs production:

factory FactoryLazySingleton({bool isProduction = false}) {
  if (isProduction) {
    // Apply production configuration
    _instance.configure(productionSettings);
  } else {
    // Apply development configuration
    _instance.configure(devSettings);
  }
  return _instance;
}

Pros

  1. You can add logic before returning an instance

  2. You can cache or reuse the same object

  3. You can dynamically return a subtype if needed

  4. You avoid unnecessary instantiation

  5. You can inject configuration or environment logic

Cons

  1. Adds slight complexity compared to simple getter access

  2. The factory constructor syntax might confuse developers unfamiliar with the pattern

  3. If overused with complex logic, it can make debugging harder

  4. Can create misleading code where FactoryLazySingleton() looks like it creates a new instance but doesn't

When Not to Use a Singleton

While singletons are powerful, they're not always the right solution. Understanding when to avoid them is just as important as knowing when to use them.

Why Singletons Can Be Problematic

Singletons create global state, which can make your application harder to reason about and test. They introduce tight coupling between components that shouldn't necessarily know about each other, and they can make it difficult to isolate components for unit testing.

Scenarios Where You Should Avoid Singletons

Avoid using the Singleton pattern if:

You need multiple independent instances

If different parts of your app need their own separate configurations or states, singletons force you into a one-size-fits-all approach.

For example, if you're building a multi-tenant application where each tenant needs isolated data, a singleton would cause data to bleed between tenants.

Alternative: Use dependency injection to pass different instances to different parts of your app. Each component receives the specific instance it needs through its constructor or a service locator.

// Instead of singleton
class UserRepository {
  final DatabaseConnection db;
  UserRepository(this.db); 
}

// Usage
final dbForTenantA = DatabaseConnection(tenantId: 'A');
final dbForTenantB = DatabaseConnection(tenantId: 'B');
final repoA = UserRepository(dbForTenantA);
final repoB = UserRepository(dbForTenantB);

Your architecture avoids shared global state

Modern architectural patterns like BLoC, Provider, or Riverpod in Flutter specifically aim to avoid global mutable state. Singletons work against these patterns by reintroducing global state.

Alternative: Use state management solutions designed for Flutter. Provider, Riverpod, BLoC, or GetX offer better ways to share data across your app while maintaining testability and avoiding tight coupling.

// Using Provider instead of singleton
class AppConfig {
  final String apiUrl;
  AppConfig(this.apiUrl);
}

// Provide it at the top level
void main() {
  runApp(
    Provider<AppConfig>(
      create: (_) => AppConfig('https://api.example.com'),
      child: MyApp(),
    ),
  );
}

// Access it anywhere in the widget tree
class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final config = Provider.of<AppConfig>(context);

  }
}

It forces tight coupling between unrelated classes

When multiple unrelated classes depend on the same singleton, they become indirectly coupled. Changes to the singleton affect all these classes, making the codebase fragile and hard to refactor.

Alternative: Use interfaces and dependency injection. Define what behavior you need through an interface, then inject implementations. This way, classes depend on abstractions, not concrete singletons.

// Define an interface
abstract class Logger {
  void log(String message);
}

// Implementation
class ConsoleLogger implements Logger {
  @override
  void log(String message) => print(message);
}

// Classes depend on the interface, not a singleton
class PaymentService {
  final Logger logger;
  PaymentService(this.logger);

  void processPayment() {
    logger.log('Processing payment');
  }
}

// Easy to test with mock
class MockLogger implements Logger {
  List<String> logs = [];
  @override
  void log(String message) => logs.add(message);
}

You need clean, isolated testing

Singletons maintain state between tests, causing test pollution where one test affects another. This makes tests unreliable and order-dependent.

Alternative: Use dependency injection and create fresh instances for each test. Most testing frameworks support this pattern, allowing you to inject mocks or fakes easily.

// Testable code
class OrderService {
  final PaymentProcessor processor;
  OrderService(this.processor);
}

// In tests
void main() {
  test('processes order successfully', () {
    final mockProcessor = MockPaymentProcessor();
    final service = OrderService(mockProcessor); 

  });
}

General Guidelines

Use singletons sparingly and only when you truly need exactly one instance of something for the entire application lifecycle. Good candidates include logging systems, application-level configuration, and hardware interface managers.

For most other cases, prefer dependency injection, state management solutions, or simply passing instances where needed. These approaches make your code more flexible, testable, and maintainable.

Conclusion

The Singleton pattern is a powerful creational tool, but like every tool, you should use it strategically.

Overusing singletons can make apps tightly coupled, hard to test, and less maintainable.

But when used correctly, the Singleton pattern helps you save memory, enforce consistency, and control object lifecycle beautifully.

The key is understanding your specific use case and choosing the right implementation approach – whether eager, lazy, or factory-based – that best serves your application's needs while maintaining clean, testable code.



Read the whole story
alvinashcraft
12 minutes ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories