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

Transitioning as a hubber

1 Share

When I joined GitHub, my legal name was Ursula—but my handle was gleeblezoid. Now, as Arthur, I’m still gleeblezoid.

Since our remote-first culture primarily uses handles, transitioning at GitHub was easier than it would have been earlier in my career. I previously worked in IT at companies that only used names for identification, which can be challenging for professionals transitioning.

I started my career doing IT support and operational work, but being interested in computers meant teaching myself how to code. A colleague from a previous role referred me to GitHub, and I started out five years ago on the IT Engineering team. After repeatedly bothering various security teams with issues and pull requests, I got adopted into the Enterprise Security team after just six months. I’ve been there ever since.

While here, I’ve been proud to work with my team on migrating our main SaaS platform to infrastructure as code, and to be a guest speaker a handful of times at Oxford University on the subject of version control.

But another great thing about working here: GitHub offers gender affirming care related benefits to all employees in terms of covering healthcare; I can expense voice training, HRT prescriptions, and therapy among other things.

There are also less obvious things that made GitHub a safe place for me to transition. Being a remote-first company means I don’t need to agonize over what to wear to the office or who will see me on the way there. Most of my work is captured in writing within Slack or GitHub itself, so when I started voice training and eventually having my voice break on HRT I wasn’t spending the whole day talking to people out loud.

We have the kind of culture where my main avatar can be a cartoon frog in a suit and nobody bats an eye, which removes the entire problem of people guessing my gender by my appearance.

I know a lot of people in the tech industry, and more trans people than the average person probably does. I know people who are closeted at work, people who go through bureaucratic nightmares on changing their name, and people for whom coming out at work is something they end up doing on a recurring basis with every new set of people they interact with.

I’ve not experienced that. Outside of the understandable bureaucratic friction of changing my name in places like payroll it’s been smooth. My team call me what I want to be called and treat me like a regular human being—as has everyone else at work I’ve interacted with. I updated my name and pronouns on our internal systems, and that was that.

Being trans isn’t easy or universally accepted. I’m not sure when I’ll next get to see my overseas teammates in person, for example. It’s also not an experience solely defined by hardship or social barriers. There is a great deal of joy in showing up as yourself and in sharing that joy with others. I nearly cried on a Zoom call when I heard someone use my name and refer to me as “him” for the first time at work, and I absolutely did cry when one of my teammates sent me a shaving kit in the mail.

Every hubber I have mentioned my transition to has been genuinely happy for me. Several of them expressed this through Arthur the aardvark, and Monty Python Holy Grail memes (we are geeks after all).

I’ve always been a man, I just needed time and support to live as one and participate as one in society. In most settings I need to explain to people how Ursula Searle became Arthur Searle, but at GitHub I’ve thankfully always been gleeblezoid.

The post Transitioning as a hubber appeared first on The GitHub Blog.

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

This Week in AI: Who Controls the Loop?

1 Share

This week host and Turing Post founder Ksenia Se threaded the latest news into a single argument: AI is moving out of conversation and into the operational loops where real work happens. From SpaceX’s $60 billion acquisition in the developer tools market to the G7’s debate about frontier model access to image generation company Midjourney’s pivot to medical hardware, the stories all pointed in the same direction.

When agents own the loop, the IDE becomes infrastructure

SpaceX’s acquisition of Anysphere, the company behind Cursor, for a reported $60 billion in stock is the kind of deal that looks straightforward until you think about what Cursor actually is. On the surface, it’s a popular AI-assisted code editor. (It’s also one of many in a highly competitive market.) However, Ksenia argued that that’s thinking too small, especially for Elon Musk. SpaceX may be angling to position Cursor as the new center of software work, in the same way GitHub became the center of the previous era.

In the old model, GitHub owned the pull request. But in the new model, the question of who owns the full loop where agents read a repo, write code, open pull requests, run tests, handle failures, and enforce engineering standards is still open. GitHub still owns the system of record and is moving to defend it: Chief product officer Mario Rodriguez recently told Turing Post that GitHub’s mission has shifted from human-developer collaboration to developer-and-agent collaboration, with the platform becoming agent-native across its APIs, UX, and underlying infrastructure. But as Ksenia explained, “Cursor’s advantage is that it owns the developer’s active coding surface” where the work starts.

If agents write more code than humans, software infrastructure should be redesigned around agents from the start. Cursor was built for agents. GitHub was built for humans and is now playing catch-up. That architectural choice may matter more than any individual product feature.

Frontier AI access is becoming a geopolitical question

The G7 summit this week included discussions about a “trusted partners” framework that would give select allied nations access to advanced US AI models, following a US order that restricted foreign nationals from accessing Anthropic’s frontier systems on national security grounds. AI models that can write software, find vulnerabilities, and operate across tools are capability systems, not just productivity software. The access rules are catching up to that reality, although as Ksenia noted, things haven’t yet come into complete focus.

For a long time, AI regulation sounded like: How do we label synthetic media? How do we reduce hallucinations, prevent bias, make chatbots safer? Now the question is so much bigger. Who can use these capable systems? Can allies use them? Can cybersecurity firms outside the US use them? Can non-US employees at US labs use them? Can European companies use American models if those models are also strategically sensitive? This isn’t traditional software licensing anymore. This is capability access control.

The underlying tension behind the G7 conversation is the dual-use problem: A model capable enough to find software vulnerabilities for defense can also find them for offense. The “trusted partners” framework reflects the new geopolitics of AI as countries jockey with rivals to secure strategic benefits for themselves and their allies. It represents an alliance layer for AI access that applies access structures previously reserved for physical military hardware to capabilities too strategically important to make fully open and too useful to keep entirely locked down. As Ksenia noted, the alliance is “not literally NATO, but [it is founded on] the same kind of logic.”

But access restrictions might also impact the talent that built these systems, who are increasingly not citizens of the country trying to control it. For instance, AI researcher Andrej Karpathy, recently hired by Anthropic, is publicly described as Slovak-Canadian. If access controls apply to non-US citizens, he and others like him may be denied access to the very systems they’ve been hired to work on. It’s an area we’ll continue to watch closely.

AI is entering the measurement loop

Midjourney, the company you probably associate with AI-generated images, has announced a new medical division and a full-body ultrasound scanner built around water immersion, developed in partnership with medical imaging hardware maker Butterfly Network. The device is designed to scan the entire body in 60 seconds: A person descends into a shallow pool on a motorized platform, passing through a ring of roughly half a million ultrasound sensors, each functioning as both a transmitter and receiver. The system uses over two petaflops of processing power to reconstruct a 3D body map from the returning wave data. Midjourney says the resulting images look comparable to today’s MRI output at a fraction of the cost and time, though that claim still needs serious clinical validation before it can stand.

The current prototype uses 40 Butterfly ultrasound-on-chip devices per system, according to a disclosure from Butterfly Network, which confirmed its codevelopment and licensing agreement with Midjourney. Midjourney plans to open a facility in San Francisco in 2027, embedding its device in a spa environment alongside hot tubs, saunas, and cold plunges. Diagnostic medical uses will require FDA approval; the initial focus is body composition mapping.

If Midjourney can build a library of full-body scans taken over months and years, that longitudinal record would give doctors and AI health tools a level of baseline data that doesn’t currently exist at scale outside of clinical trials. That’s the same structural logic Ksenia traced through Cursor and GitHub: The value compounds inside the loop through repeated, precise measurement over time. Midjourney is positioning itself to own that loop in the health domain.

What’s next

The competition for AI advantage is moving from model capability to infrastructure position. Who owns the coding loop? Who controls access to frontier systems? Who builds the measurement environment where health data accumulates over time? Those questions are about where intelligence meets operational reality, not which model scores highest on a benchmark.

Hiring news from the week reinforces how seriously the labs are treating this phase. John Jumper, the Nobel laureate who shared the prize with Demis Hassabis for AlphaFold, left Google DeepMind for Anthropic. Noam Shazeer, one of the coauthors of “Attention Is All You Need,” reportedly left Google for OpenAI after Google paid approximately $2.7 billion to bring him back in 2024. The labs are betting on scientific talent at the same time they’re betting on infrastructure.

Next week, host Andreas Welsch will be back to discuss multi-vendor strategy with Conductor’s Matt Palmer. They’ll cover Sakana’s launch of Fugu, Qualcomm’s ~$4B move for Modular, Anthropic’s Claude Tag stepping into Slack as a virtual coworker, Samsung putting ChatGPT and Codex in front of its entire workforce, and more. Register here to attend live.

Starting in July, the live event will move inside the O’Reilly learning platform, but we’ll continue to publish our takeaways here on Radar each Friday and share full episodes on YouTube, Spotify, and Apple.



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

The PO Who Doesn't Care vs the PO Who Always Has the Answer | Olaitan Fashanu

1 Share

Olaitan Fashanu: The PO Who Doesn't Care vs the PO Who Always Has the Answer

Read the full Show Notes and search through the world's largest audio library on Agile and Scrum directly on the Scrum Master Toolbox Podcast website: http://bit.ly/SMTP_ShowNotes.

 

In this episode, we refer to a recurring theme in past podcast episodes—the proxy product owner who can't make decisions because they're not theirs to make.

The Great Product Owner: Always Available, Always Decisive, Always Has the Context

Read the full Show Notes and search through the world's largest audio library on Agile and Scrum directly on the Scrum Master Toolbox Podcast website: http://bit.ly/SMTP_ShowNotes.

 

"There was nothing you tell, any questions you have about a particular feature that this guy doesn't have an answer to. And that really moved the team so fast." - Olaitan Fashanu

 

The best PO Olaitan ever worked with was the mirror opposite of every anti-pattern he'd seen. Deeply involved in refinement. Took backlog management seriously. Always brought the context. Always available to the team. And—maybe most importantly—always ready to make a decision when devs surfaced trade-offs. The team could ask any question about any feature, and the answer was right there. Not "let me check," not "I'll get back to you," not "what do you think?"—a decision. That single quality, Olaitan says, was what moved the team faster than anything else. As a Scrum Master, when you see a great PO at work, you also see the amplifying waves of impact: motivation rises, quality rises, ownership grows. Olaitan's takeaway is sharp: the success of our job depends on how well the product owner does theirs.

 

Self-reflection Question: When was the last time your PO made a real-time decision that unblocked the team in a single conversation—and what's preventing that from being the norm?

The Bad Product Owner: Doesn't Care About Impact, Can't Make Decisions

Read the full Show Notes and search through the world's largest audio library on Agile and Scrum directly on the Scrum Master Toolbox Podcast website: http://bit.ly/SMTP_ShowNotes.

 

"The product owner cares about delivery. We just need to release to the customer. That is something I don't like." - Olaitan Fashanu

 

Olaitan describes two anti-patterns wrapped into one bad-PO type. The first: the PO who doesn't care about the impact of their work on the team. Tickets dropped without context. No refinement. No problem framing. Just "ship by end of month." The data shows up in Jira if you're paying attention—patterns of churn, quality issues, customer complaints, slow market response. Beyond the numbers, the team loses motivation, frustration creeps in, and eventually you lose the team entirely. The second anti-pattern, layered on top: the PO who can't make decisions. Developers come back with two options and the trade-offs—and the PO can't pick one. Vasco connects it to the proxy PO pattern explored in past episodes—a PO whose decisions aren't actually theirs to make. The cost is the same either way: the team stalls, ownership erodes, and stakeholder conflict grows.

 

In this segment, we refer to the proxy PO anti-pattern explored in earlier episodes of the podcast.

 

Self-reflection Question: Is your PO unable to decide—or unable to be allowed to decide? The difference changes which conversation you need to have, and with whom.

 

[The Scrum Master Toolbox Podcast Recommends]

🔥In the ruthless world of fintech, success isn't just about innovation—it's about coaching!🔥

Angela thought she was just there to coach a team. But now, she's caught in the middle of a corporate espionage drama that could make or break the future of digital banking. Can she help the team regain their mojo and outwit their rivals, or will the competition crush their ambitions? As alliances shift and the pressure builds, one thing becomes clear: this isn't just about the product—it's about the people.

 

🚨 Will Angela's coaching be enough? Find out in Shift: From Product to People—the gripping story of high-stakes innovation and corporate intrigue.

 

Buy Now on Amazon

 

[The Scrum Master Toolbox Podcast Recommends]

 

About Olaitan Fashanu

 

Olaitan Fashanu is a customer-focused professional with expertise in product management, technology, and coaching. He drives digital and agile transformation, builds collaborative cross-functional teams, and delivers high-quality products across markets. Curious and strategic, he explores AI and data intelligence while balancing technical depth, business goals, culture, structure, and long-term vision.

 

You can link with Olaitan Fashanu on LinkedIn.





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

AGL 475: Marissa McCourry

1 Share

About Marissa

Marissa is an executive coach and leadership development expert who coaches high-achieving senior leaders in need of external perspective. Her career spans academia, consulting, and corporate leadership, giving her a well-rounded point of view on how leaders are groomed and supported for the future. She has designed executive education programs at Thunderbird School of Global Management and the University of Arizona, built large-scale experiential and simulation-based leadership programs with BTS (a global consulting firm), and led enterprise-wide talent and leadership strategies as Director of Global Leadership Development at Vanguard and Visa.

Through her firm, mFluence Coaching & Consulting, Marissa partners with leaders to translate insight into action—helping them adopt new mindsets, shift behaviors, and achieve measurable results in both their professional and personal lives. She measures success by her clients’ breakthroughs: the “aha” moments that lead to lasting change.

What sets Marissa apart is her ability to bring three lenses to every engagement: academic rigor (for proven, evidence-based science), external consulting expertise, and firsthand corporate experience developing senior leaders. She understands leadership theory and how it works in real organizational life when the rubber meets the road.


Today We Talked About

  • Background
  • Leadership
  • Emotional Intelligence
  • What Coaches don’t do
  • What Coaches do
  • EQ Assessments
  • Happiness
  • Improving your EQ
  • Self-Awareness

Connect With Marissa


Leave me a tip $
Click here to Donate to the show


I hope you enjoyed this show, please head over to Apple Podcasts and subscribe and leave me a rating and review, even one sentence will help spread the word.  Thanks again!





Download audio: https://media.blubrry.com/a_geek_leader_podcast__/mc.blubrry.com/a_geek_leader_podcast__/AGL_475_Marissa_McCourry.mp3?awCollectionId=300549&awEpisodeId=12070257&aw_0_azn.pgenre=Business&aw_0_1st.ri=blubrry&aw_0_azn.pcountry=US&aw_0_azn.planguage=en&cat_exclude=IAB1-8%2CIAB1-9%2CIAB7-41%2CIAB8-5%2CIAB8-18%2CIAB11-4%2CIAB25%2CIAB26&aw_0_cnt.rss=https%3A%2F%2Fwww.ageekleader.com%2Ffeed%2Fpodcast
Read the whole story
alvinashcraft
41 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

Business Applications Built for Microsoft 365 – Cubic Logics – SharePoint Partner Showcase

1 Share

Organizations increasingly view Microsoft 365 as more than a productivity platform. They use it to streamline business processes, modernize operations, and empower employees with solutions that integrate into the tools they use every day.

For more than 11 years, Manoj Kumar, Anirban Mitra, and the team at Cubic Logics have helped organizations extend the value of Microsoft 365 through business applications built on SharePoint and the broader Microsoft ecosystem. Through their Apps365 portfolio, customers can deploy solutions for contract management, human resources, help desk operations, asset management, learning management, expense management, and other business scenarios directly within their existing Microsoft 365 environment. 

With more than 12,100 deployments across 172 countries, Cubic Logics serves organizations ranging from small and medium businesses to large enterprises, government agencies, nonprofits, and highly regulated industries. Their solutions help customers accelerate time to value while leveraging existing investments in SharePoint, Teams, Power Automate, Microsoft security capabilities, and increasingly, Microsoft Copilot.

What stands out in Cubic Logics' approach is their focus on bringing business applications closer to where users already work. Rather than introducing disconnected systems, their solutions integrate directly into Microsoft 365, creating a unified and collaborative experience that helps organizations improve business processes while maintaining governance, compliance, privacy, and security requirements. 

As artificial intelligence continues to reshape workplace experiences, Cubic Logics is also helping customers adopt AI-powered solutions by combining Microsoft Copilot, agents, Azure OpenAI, and other AI technologies with their existing business processes and data. This enables organizations to unlock new insights, automate routine tasks, and extend the reach of AI across both Microsoft 365 and connected business applications. 

Beyond their commercial offerings, Cubic Logics is actively contributing to the Microsoft 365 and SharePoint community through open-source projects and reusable components. Their recent open-source initiative begins with the 1-on-1 Sharing web part (coming soon), designed to help organizations structure recurring meetings through templates, actions, automation, and AI-assisted insights. This reflects their ongoing commitment to giving back to the ecosystem that has supported their growth and success over the past decade. 

In this SharePoint Partner Showcase, Vesa Juvonen sat down with Manoj Kumar and Anirban Mitra to discuss the Apps365 platform, their vision for business applications in Microsoft 365, the opportunities created by Copilot and AI, and their recent investments in open source and community contributions. 

Learn more about Apps365 and Cubic Logics:

Microsoft 365 and SharePoint provide powerful out-of-the-box capabilities that can be extended and tailored to your user experience goals using no-code, low-code, or pro-code approaches. This flexibility enables customers to configure and build unique and powerful experiences at Microsoft 365 for corporate communications, employee experiences, and business processes.

 

Do you have SharePoint-based solutions that end users can benefit from and would like to showcase to the community? Let us highlight your solution(s). We welcome all kinds of solutions which highlight the art of possible with SharePoint. Please fill in the following form to get us connected with you and we will get back to you on planning the right model together - https://aka.ms/sharepoint/partner/showcase.

The Microsoft 365 ecosystem continues to thrive through partners who innovate, contribute, and share with the broader community. Amazing ecosystem building AI and Copilot powered solutions with SharePoint and Microsoft 365 👏

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

Service Communication Patterns in .NET Core and Azure

1 Share

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")
            }
        };

        await _sender.SendMessageAsync(message, cancellationToken);
    }
}

// Consumer using BackgroundService
public class InventoryService : BackgroundService
{
    private readonly ServiceBusProcessor _processor;
    private readonly ILogger<InventoryService> _logger;
    private readonly IInventoryRepository _repository;

    public InventoryService(
        ServiceBusClient client, 
        ILogger<InventoryService> logger,
        IInventoryRepository repository)
    {
        _processor = client.CreateProcessor("order-events", "inventory-subscription");
        _processor.ProcessMessageAsync += ProcessMessageHandler;
        _processor.ProcessErrorAsync += ErrorHandler;
        _logger = logger;
        _repository = repository;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        await _processor.StartProcessingAsync(stoppingToken);

        // .NET 9: Improved cancellation handling
        await Task.Delay(Timeout.InfiniteTimeSpan, stoppingToken);
    }

    public override async Task StopAsync(CancellationToken cancellationToken)
    {
        await _processor.StopProcessingAsync(cancellationToken);
        await base.StopAsync(cancellationToken);
    }

    private async Task ProcessMessageHandler(ProcessMessageEventArgs args)
    {
        try
        {
            var order = JsonSerializer.Deserialize<Order>(args.Message.Body);

            // Reserve inventory with idempotency check
            await _repository.ReserveInventoryAsync(order!, args.CancellationToken);

            // Complete the message
            await args.CompleteMessageAsync(args.Message, args.CancellationToken);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error processing order message {MessageId}", args.Message.MessageId);

            // Dead-letter the message if processing fails repeatedly
            if (args.Message.DeliveryCount > 3)
            {
                await args.DeadLetterMessageAsync(args.Message, 
                    "Processing failed after retries", 
                    ex.Message, 
                    args.CancellationToken);
            }
            else
            {
                await args.AbandonMessageAsync(args.Message, cancellationToken: args.CancellationToken);
            }
        }
    }

    private Task ErrorHandler(ProcessErrorEventArgs args)
    {
        _logger.LogError(args.Exception, "Service Bus processor error: {ErrorSource}", args.ErrorSource);
        return Task.CompletedTask;
    }
}

Dependency Injection Setup for .NET 9

// Program.cs with .NET 9 improvements
var builder = WebApplication.CreateBuilder(args);

// Azure Service Bus with managed identity
builder.Services.AddAzureClients(clientBuilder =>
{
    clientBuilder.AddServiceBusClient(builder.Configuration.GetSection("ServiceBus"))
        .WithCredential(new DefaultAzureCredential());
});

// Register background services
builder.Services.AddHostedService<InventoryService>();
builder.Services.AddSingleton<OrderCreatedPublisher>();

// Add health checks for Service Bus
builder.Services.AddHealthChecks()
    .AddAzureServiceBusQueue(
        builder.Configuration["ServiceBus:ConnectionString"]!,
        "order-events");

Pub/Sub vs Producer/Consumer Patterns

Understanding when to use each pattern is crucial for effective system design.

Publish-Subscribe Pattern

In pub/sub, events are broadcast to multiple independent subscribers who each receive a copy of every message. This pattern excels at:

  • Event notification across multiple services
  • Decoupling event producers from consumers
  • Allowing new subscribers without changing publishers
  • Broadcasting state changes to interested parties

Implementation Options:

  • Azure Service Bus Topics: Durable messaging with advanced filtering
  • Azure Event Grid: Lightweight, serverless event routing

Producer-Consumer Pattern

Producer-consumer implements point-to-point communication where each message is processed by exactly one consumer. Ideal for:

  • Work queue distribution
  • Load balancing across workers
  • Task processing with guaranteed completion
  • Rate limiting and backpressure handling

Implementation Options:

  • Azure Service Bus Queues: Enterprise features with transactions
  • Azure Queue Storage: Simple, cost-effective queuing
  • RabbitMQ: Self-hosted option with rich features

Azure Event Grid Example

public class EventGridPublisher
{
    private readonly EventGridPublisherClient _client;
    private readonly ILogger<EventGridPublisher> _logger;

    public EventGridPublisher(EventGridPublisherClient client, ILogger<EventGridPublisher> logger)
    {
        _client = client;
        _logger = logger;
    }

    public async Task PublishOrderEventAsync(OrderCreated orderEvent, CancellationToken cancellationToken = default)
    {
        var cloudEvent = new CloudEvent(
            source: "OrderService",
            type: "Order.Created",
            jsonSerializableData: orderEvent)
        {
            Id = Guid.NewGuid().ToString(),
            Time = DateTimeOffset.UtcNow,
            // .NET 9: Enhanced structured data support
            ExtensionAttributes = 
            {
                ["correlationId"] = orderEvent.CorrelationId,
                ["orderAmount"] = orderEvent.TotalAmount
            }
        };

        try
        {
            await _client.SendEventAsync(cloudEvent, cancellationToken);
            _logger.LogInformation("Published order event {OrderId}", orderEvent.OrderId);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to publish order event {OrderId}", orderEvent.OrderId);
            throw;
        }
    }
}

Azure Event Grid vs Azure Service Bus

When to Use Event Grid

Event Grid excels in event-driven architectures requiring:

  • Serverless integration: Trigger Azure Functions, Logic Apps
  • React to Azure service events: Storage changes, resource updates
  • Real-time notifications: Push updates to web clients
  • High throughput: Millions of events per second
  • Low latency: Sub-second delivery
  • Pay per event: Cost-effective for sporadic events

Best for: State change notifications, IoT telemetry, serverless workflows, webhook delivery

When to Use Service Bus

Service Bus is designed for enterprise messaging requiring:

  • Message sessions: Ordered processing of related messages
  • Transactions: ACID guarantees across operations
  • Dead-letter queues: Handle poison messages
  • Scheduled delivery: Defer message processing
  • Duplicate detection: Automatic deduplication
  • Large messages: Up to 100MB payloads

Best for: Order processing, financial transactions, workflow orchestration, hybrid cloud integration

Commands and Queries with CQRS

Command Query Responsibility Segregation separates write operations (commands) from read operations (queries), enabling independent optimization and scaling.

MediatR in .NET 9

MediatR provides in-process mediator pattern implementation with .NET 9 performance improvements:

// Command with validation
public record CreateOrderCommand(Guid CustomerId, List<OrderLineDto> Items) 
    : IRequest<Result<Guid>>;

// Fluent validation
public class CreateOrderCommandValidator : AbstractValidator<CreateOrderCommand>
{
    public CreateOrderCommandValidator()
    {
        RuleFor(x => x.CustomerId).NotEmpty();
        RuleFor(x => x.Items).NotEmpty();
        RuleForEach(x => x.Items).SetValidator(new OrderLineValidator());
    }
}

// Command Handler with event publishing
public class CreateOrderCommandHandler 
    : IRequestHandler<CreateOrderCommand, Result<Guid>>
{
    private readonly IOrderRepository _repository;
    private readonly IPublisher _publisher;
    private readonly ILogger<CreateOrderCommandHandler> _logger;

    public async Task<Result<Guid>> Handle(
        CreateOrderCommand request, 
        CancellationToken cancellationToken)
    {
        try
        {
            // Create aggregate
            var order = Order.Create(request.CustomerId, request.Items);
            await _repository.AddAsync(order, cancellationToken);

            // Publish domain event
            await _publisher.Publish(
                new OrderCreatedEvent(order.Id, order.CustomerId, order.TotalAmount), 
                cancellationToken);

            _logger.LogInformation("Order {OrderId} created for customer {CustomerId}", 
                order.Id, order.CustomerId);

            return Result<Guid>.Success(order.Id);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to create order for customer {CustomerId}", request.CustomerId);
            return Result<Guid>.Failure("Failed to create order");
        }
    }
}

// Query with projection
public record GetOrderQuery(Guid OrderId) : IRequest<Result<OrderDto>>;

// Query Handler using read model
public class GetOrderQueryHandler 
    : IRequestHandler<GetOrderQuery, Result<OrderDto>>
{
    private readonly IOrderReadRepository _repository;
    private readonly IMemoryCache _cache;

    public async Task<Result<OrderDto>> Handle(
        GetOrderQuery request, 
        CancellationToken cancellationToken)
    {
        // .NET 9: Improved caching with GetOrCreateAsync
        var order = await _cache.GetOrCreateAsync(
            $"order:{request.OrderId}",
            async entry =>
            {
                entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5);
                return await _repository.GetByIdAsync(request.OrderId, cancellationToken);
            });

        return order is not null 
            ? Result<OrderDto>.Success(order) 
            : Result<OrderDto>.Failure("Order not found");
    }
}

Pipeline Behaviors for Cross-Cutting Concerns

// Validation behavior
public class ValidationBehavior<TRequest, TResponse> 
    : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IRequest<TResponse>
{
    private readonly IEnumerable<IValidator<TRequest>> _validators;

    public async Task<TResponse> Handle(
        TRequest request,
        RequestHandlerDelegate<TResponse> next,
        CancellationToken cancellationToken)
    {
        if (!_validators.Any()) return await next();

        var context = new ValidationContext<TRequest>(request);
        var failures = _validators
            .Select(v => v.Validate(context))
            .SelectMany(result => result.Errors)
            .Where(f => f != null)
            .ToList();

        if (failures.Count != 0)
            throw new ValidationException(failures);

        return await next();
    }
}

// Logging behavior with .NET 9 structured logging
public class LoggingBehavior<TRequest, TResponse> 
    : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IRequest<TResponse>
{
    private readonly ILogger<LoggingBehavior<TRequest, TResponse>> _logger;

    public async Task<TResponse> Handle(
        TRequest request,
        RequestHandlerDelegate<TResponse> next,
        CancellationToken cancellationToken)
    {
        var requestName = typeof(TRequest).Name;

        _logger.LogInformation("Handling {RequestName}", requestName);
        var stopwatch = Stopwatch.StartNew();

        try
        {
            var response = await next();
            stopwatch.Stop();

            _logger.LogInformation("Handled {RequestName} in {ElapsedMs}ms", 
                requestName, stopwatch.ElapsedMilliseconds);

            return response;
        }
        catch (Exception ex)
        {
            stopwatch.Stop();
            _logger.LogError(ex, "Error handling {RequestName} after {ElapsedMs}ms", 
                requestName, stopwatch.ElapsedMilliseconds);
            throw;
        }
    }
}

Common Anti-Patterns and Solutions

SignalR and CQRS Anti-Pattern

Using SignalR hubs for synchronous command-response patterns defeats the purpose of CQRS and creates a fragile, tightly-coupled system.

Anti-Pattern: Synchronous Command Over WebSocket

// BAD: Treating SignalR as synchronous RPC
public class OrderHub : Hub
{
    private readonly IOrderService _orderService;

    // This is NOT true CQRS - it's synchronous RPC over WebSockets
    public async Task<OrderResult> SubmitOrder(CreateOrderRequest request)
    {
        // Caller waits for complete processing
        return await _orderService.ProcessOrderAsync(request);
    }
}

Problems with this approach:

  • Couples UI latency directly to backend processing time
  • WebSocket connection can timeout or drop during processing
  • No retry mechanism if connection fails
  • Cannot scale command processing independently
  • Creates artificial synchronous behavior in an async system

Recommended Pattern: Async Command Processing with SignalR Notifications

Implement true asynchronous command handling with status updates pushed via SignalR:

// GOOD: Async command submission with correlation
public class OrderHub : Hub
{
    private readonly ServiceBusSender _commandSender;
    private readonly ILogger<OrderHub> _logger;

    public async Task<CommandAccepted> SubmitOrder(CreateOrderRequest request)
    {
        var correlationId = Guid.NewGuid().ToString();

        // Add user to correlation group for status updates
        await Groups.AddToGroupAsync(Context.ConnectionId, correlationId);

        // Enqueue command to Service Bus
        var command = new CreateOrderCommand(request.CustomerId, request.Items)
        {
            CorrelationId = correlationId,
            UserId = Context.User?.Identity?.Name
        };

        var message = new ServiceBusMessage(JsonSerializer.Serialize(command))
        {
            MessageId = correlationId,
            Subject = "CreateOrder",
            ApplicationProperties = { ["UserId"] = command.UserId }
        };

        await _commandSender.SendMessageAsync(message);

        _logger.LogInformation("Order command queued with correlation {CorrelationId}", correlationId);

        // Return 202 Accepted immediately
        return new CommandAccepted(correlationId, "Order is being processed");
    }
}

// Command handler processes asynchronously
public class CreateOrderCommandHandler : IConsumer<CreateOrderCommand>
{
    private readonly IOrderRepository _repository;
    private readonly IHubContext<OrderHub> _hubContext;
    private readonly IPublisher _publisher;

    public async Task Consume(ConsumeContext<CreateOrderCommand> context)
    {
        var command = context.Message;

        try
        {
            // Send processing notification
            await _hubContext.Clients.Group(command.CorrelationId)
                .SendAsync("OrderStatusUpdate", new { Status = "Processing", command.CorrelationId });

            // Process business logic
            var order = Order.Create(command.CustomerId, command.Items);
            await _repository.AddAsync(order);

            // Publish domain events to update read models
            await _publisher.Publish(new OrderCreatedEvent(order.Id, command.CorrelationId));

            // Send completion notification
            await _hubContext.Clients.Group(command.CorrelationId)
                .SendAsync("OrderCompleted", new { order.Id, command.CorrelationId });
        }
        catch (Exception ex)
        {
            // Send error notification
            await _hubContext.Clients.Group(command.CorrelationId)
                .SendAsync("OrderFailed", new { Error = ex.Message, command.CorrelationId });
        }
    }
}

// Read model updater for queries
public class OrderProjection : IEventHandler<OrderCreatedEvent>
{
    private readonly IOrderReadRepository _readRepository;

    public async Task Handle(OrderCreatedEvent evt, CancellationToken cancellationToken)
    {
        // Update denormalized read model for fast queries
        await _readRepository.UpsertAsync(evt.OrderId, evt.ToDto(), cancellationToken);
    }
}

Complete Async Flow Architecture

  1. Client → SignalR Hub: Submit command, receive correlation ID immediately
  2. Hub: Validate, enqueue to message broker, return 202 Accepted
  3. Command Handler: Process business logic, emit domain events, update write model
  4. Event Handlers: Update read models and projections
  5. Notifications: Push progress via SignalR using correlation ID
  6. Client: Query read model endpoint for latest state

Key Principles

CQRS Does Not Require Async: CQRS fundamentally means separating command and query models. You can implement commands synchronously in a monolith and still follow CQRS. Asynchrony and message queues are architectural choices for scalability and decoupling, not strict CQRS requirements.

Eventual Consistency is Common: When using async commands with CQRS, read models are typically eventually consistent. This is acceptable for most business scenarios and enables independent scaling.

Implementation Best Practices

Transport Layer: Use Rebus or MassTransit with Azure Service Bus for command transport. Reserve SignalR exclusively for pushing notifications to clients, never for inter-service communication.

Outbox Pattern: Implement transactional outbox to ensure commands and events are persisted atomically with domain changes, guaranteeing at-least-once delivery without duplicates.

Idempotency: Make all command handlers idempotent using command IDs or correlation IDs to safely handle retries and duplicate messages.

Correlation: Thread correlation IDs through logs, messages, and SignalR groups to trace operations across distributed components.

Security: Never trust hub payloads. Revalidate all commands in handlers. Use per-user or per-correlation groups in SignalR to prevent unauthorized access to status updates.

User Experience: Return fast with 202 Accepted, display processing status, push updates via SignalR, and query the read model endpoint for authoritative data.

Chatty Services Anti-Pattern

Chatty services make excessive synchronous calls between services, creating network overhead, increased latency, and tight coupling.

Example of Anti-Pattern:

// BAD: Multiple sequential synchronous calls
public async Task<OrderSummary> GetOrderSummaryAsync(Guid orderId)
{
    // Each call waits for the previous to complete
    var order = await _orderClient.GetOrderAsync(orderId);
    var customer = await _customerClient.GetCustomerAsync(order.CustomerId);
    var payment = await _paymentClient.GetPaymentAsync(orderId);
    var shipping = await _shippingClient.GetShippingAsync(orderId);
    var inventory = await _inventoryClient.GetInventoryStatusAsync(orderId);

    // High latency: sum of all service calls plus network overhead
    return new OrderSummary(order, customer, payment, shipping, inventory);
}

Problems:

  • Total latency is sum of all service calls
  • Single point of failure if any service is unavailable
  • Tight coupling between services
  • Poor scalability under load
  • Difficult to version and evolve independently

Solution: Materialized Views and CQRS

Implement event-driven read models that aggregate data asynchronously:

// GOOD: Query pre-aggregated materialized view
public class OrderSummaryQueryHandler : IRequestHandler<GetOrderSummaryQuery, OrderSummaryDto>
{
    private readonly IOrderSummaryReadRepository _repository;
    private readonly IMemoryCache _cache;

    public async Task<OrderSummaryDto> Handle(
        GetOrderSummaryQuery request, 
        CancellationToken cancellationToken)
    {
        // Query optimized read model - single database call
        var summary = await _cache.GetOrCreateAsync(
            $"order-summary:{request.OrderId}",
            async entry =>
            {
                entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5);
                return await _repository.GetOrderSummaryAsync(request.OrderId, cancellationToken);
            });

        return summary;
    }
}

// Update materialized view via domain events
public class OrderSummaryProjection : 
    IEventHandler<OrderCreatedEvent>,
    IEventHandler<PaymentCompletedEvent>,
    IEventHandler<ShippingUpdatedEvent>
{
    private readonly IOrderSummaryReadRepository _repository;

    public async Task Handle(OrderCreatedEvent evt, CancellationToken cancellationToken)
    {
        var summary = new OrderSummaryDto
        {
            OrderId = evt.OrderId,
            CustomerId = evt.CustomerId,
            TotalAmount = evt.TotalAmount,
            Status = "Created",
            CreatedAt = evt.CreatedAt
        };

        await _repository.UpsertAsync(summary, cancellationToken);
    }

    public async Task Handle(PaymentCompletedEvent evt, CancellationToken cancellationToken)
    {
        await _repository.UpdatePaymentStatusAsync(
            evt.OrderId, 
            "Paid", 
            evt.PaymentMethod, 
            cancellationToken);
    }

    public async Task Handle(ShippingUpdatedEvent evt, CancellationToken cancellationToken)
    {
        await _repository.UpdateShippingStatusAsync(
            evt.OrderId, 
            evt.Status, 
            evt.TrackingNumber, 
            cancellationToken);
    }
}

Alternative: Backend for Frontend (BFF) Pattern

For scenarios where real-time consistency is required, implement a BFF that intelligently aggregates:

// BFF with parallel calls and circuit breaker
public class OrderBffService
{
    private readonly IOrderClient _orderClient;
    private readonly ICustomerClient _customerClient;
    private readonly IPaymentClient _paymentClient;
    private readonly ILogger<OrderBffService> _logger;

    public async Task<OrderSummary> GetOrderSummaryAsync(
        Guid orderId, 
        CancellationToken cancellationToken)
    {
        // Execute calls in parallel to reduce total latency
        var orderTask = _orderClient.GetOrderAsync(orderId, cancellationToken);

        // Wait for order to get customer ID
        var order = await orderTask;

        // Now parallel calls for remaining data
        var customerTask = _customerClient.GetCustomerAsync(order.CustomerId, cancellationToken);
        var paymentTask = _paymentClient.GetPaymentAsync(orderId, cancellationToken);
        var shippingTask = _shippingClient.GetShippingAsync(orderId, cancellationToken);

        await Task.WhenAll(customerTask, paymentTask, shippingTask);

        return new OrderSummary(
            order, 
            customerTask.Result, 
            paymentTask.Result, 
            shippingTask.Result);
    }
}

Monitoring Chatty Services

Proactively identify chatty service patterns using Azure Application Insights:

// Configure Application Insights with enhanced dependency tracking
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddApplicationInsightsTelemetry(options =>
{
    options.EnableDependencyTrackingTelemetryModule = true;
    options.EnableRequestTrackingTelemetryModule = true;
    options.ConnectionString = builder.Configuration["ApplicationInsights:ConnectionString"];
});

// Add custom telemetry for detailed tracking
builder.Services.AddSingleton<ITelemetryInitializer, ServiceCallTelemetryInitializer>();

KQL Queries for Detection

Use Kusto Query Language to identify problematic patterns:

// Detect excessive service-to-service calls
dependencies
| where timestamp > ago(1h)
| where type == "HTTP" or type == "Azure Service Bus"
| summarize 
    CallCount = count(), 
    AvgDuration = avg(duration),
    P95Duration = percentile(duration, 95),
    P99Duration = percentile(duration, 99)
    by target, name, operation_Name
| where CallCount > 100
| order by CallCount desc

// Identify sequential call chains (chatty pattern indicator)
requests
| where timestamp > ago(1h)
| join kind=inner (
    dependencies
    | where timestamp > ago(1h)
) on operation_Id
| summarize 
    DependencyCount = dcount(name),
    TotalDuration = sum(duration),
    RequestDuration = max(duration1)
    by operation_Id, operation_Name
| where DependencyCount > 5
| order by DependencyCount desc

// Find slow operations with many dependencies
requests
| where timestamp > ago(24h)
| where duration > 1000 // over 1 second
| join kind=inner (
    dependencies
    | summarize DepCount = count(), DepDuration = sum(duration) by operation_Id
) on operation_Id
| where DepCount > 3
| project 
    timestamp, 
    operation_Name, 
    duration, 
    DepCount, 
    DepDuration,
    ChattyRatio = DepDuration / duration * 100
| order by ChattyRatio desc

Alerting Setup

Create proactive alerts in Azure Monitor:

// High dependency call rate alert
{
    "name": "High Service Dependency Calls",
    "description": "Alert when service makes excessive calls to dependencies",
    "severity": 2,
    "evaluationFrequency": "PT5M",
    "windowSize": "PT15M",
    "criteria": {
        "allOf": [{
            "query": "dependencies | where timestamp > ago(15m) | summarize count() by target | where count_ > 500",
            "threshold": 0,
            "operator": "GreaterThan"
        }]
    }
}

Advanced Patterns in .NET 9

Using Keyed Services for Multi-Tenant Messaging

.NET 9 introduces keyed services for better dependency injection:

// Register multiple Service Bus clients for different tenants
builder.Services.AddKeyedSingleton<ServiceBusClient>("tenant-a", (sp, key) =>
    new ServiceBusClient(builder.Configuration["ServiceBus:TenantA:ConnectionString"]));

builder.Services.AddKeyedSingleton<ServiceBusClient>("tenant-b", (sp, key) =>
    new ServiceBusClient(builder.Configuration["ServiceBus:TenantB:ConnectionString"]));

// Use in services
public class MultiTenantOrderPublisher
{
    private readonly IServiceProvider _serviceProvider;

    public async Task PublishAsync(string tenantId, Order order)
    {
        var client = _serviceProvider.GetRequiredKeyedService<ServiceBusClient>(tenantId);
        var sender = client.CreateSender("orders");
        await sender.SendMessageAsync(new ServiceBusMessage(JsonSerializer.Serialize(order)));
    }
}

TimeProvider for Testable Time-Based Operations

// Use TimeProvider abstraction for testable delayed messages
public class ScheduledOrderService
{
    private readonly ServiceBusSender _sender;
    private readonly TimeProvider _timeProvider;

    public ScheduledOrderService(ServiceBusSender sender, TimeProvider timeProvider)
    {
        _sender = sender;
        _timeProvider = timeProvider;
    }

    public async Task ScheduleOrderProcessingAsync(Order order, TimeSpan delay)
    {
        var message = new ServiceBusMessage(JsonSerializer.Serialize(order))
        {
            ScheduledEnqueueTime = _timeProvider.GetUtcNow().Add(delay)
        };

        await _sender.SendMessageAsync(message);
    }
}

// In tests, use FakeTimeProvider
var fakeTime = new FakeTimeProvider();
var service = new ScheduledOrderService(sender, fakeTime);

Conclusion

Building resilient distributed systems requires careful selection of communication patterns. Use asynchronous messaging for decoupling, implement CQRS properly with event-driven read models, avoid chatty service patterns with materialized views, and leverage .NET 9's enhanced performance and features for optimal results.

Monitor your services continuously with Application Insights and KQL queries to detect anti-patterns early. Following these proven patterns will help you build scalable, maintainable microservices architectures on Azure.

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