Friday, September 26, 2025

Alternative of Message Broker communication between services in microservice architecture

 In microservice architecture, a Message Broker (like RabbitMQ, Azure Service Bus, Kafka) is commonly used for asynchronous communication between services. But there are several alternatives depending on your requirements. Here’s a detailed overview:


1. Direct HTTP/REST Calls (Synchronous Communication)

  • How it works: Services call each other directly using HTTP APIs.

  • Pros: Simple, no extra infrastructure needed, easy to debug.

  • Cons: Tight coupling, less resilient, services must be available when called.

  • Use case: When low latency and real-time response are critical.

// Example in .NET Core var client = new HttpClient(); var response = await client.GetAsync("https://serviceB/api/data");

2. gRPC (Remote Procedure Calls)

  • How it works: High-performance RPC framework using Protocol Buffers.

  • Pros: Fast, strongly typed, supports streaming.

  • Cons: Slightly more complex than REST, needs HTTP/2 support.

  • Use case: Inter-service communication in performance-critical systems.

// Example in .NET Core var channel = GrpcChannel.ForAddress("https://localhost:5001"); var client = new Greeter.GreeterClient(channel); var reply = await client.SayHelloAsync(new HelloRequest { Name = "Hasitha" });

3. Event Streaming Platforms (Kafka alternative)

  • How it works: Services publish events to a stream; other services consume events.

  • Pros: Durable, scalable, decouples services.

  • Cons: More infrastructure, eventual consistency.

  • Use case: Event-driven microservices, analytics pipelines.


4. Database as a Message Queue

  • How it works: Services write “events” to a shared database table; other services poll for changes.

  • Pros: No extra messaging system needed.

  • Cons: Poor performance at scale, tight coupling, potential DB contention.

  • Use case: Small-scale systems or legacy apps.


5. In-Memory Event Bus

  • How it works: Use a lightweight in-memory bus within the process (e.g., Mediator pattern, .NET IMediator).

  • Pros: Very fast, simple for single-host microservices.

  • Cons: Doesn’t work across distributed services or multiple instances.

  • Use case: Microservices hosted in the same process/container, or for internal communication only.


6. SignalR / WebSockets


Summary Table

ApproachTypeProsConsBest For
HTTP/RESTSyncSimpleTight couplingSimple services, sync calls
gRPCSyncFast, typed, streamingNeeds HTTP/2High-performance services
Event Streaming (Kafka)AsyncDurable, scalableComplex infraEvent-driven architecture
Database QueueAsyncNo extra infraPoor performanceSmall apps
In-Memory Event BusAsyncVery fastSingle-host onlyInternal communication
SignalR/WebSocketsAsyncReal-timeNot persistentNotifications, dashboards

💡 Tip:
If your services are distributed across multiple servers or containers, replacing a message broker completely is tough. Usually, gRPC or REST can replace some use cases, but true async, decoupled communication often still benefits from a broker.

Understanding Architecture Styles, Architecture Patterns, and Design Patterns in .NET Application Development

 When building modern applications using .NET Core or .NET Framework, it’s important to make the right architectural decisions early. A well-structured architecture not only improves performance, scalability, and maintainability but also reduces technical debt in the long run. Developers often confuse Architecture Styles, Architecture Patterns, and Design Patterns, but each has a unique role in shaping enterprise-grade applications.

In this article, we’ll explore these concepts in detail, along with the major areas to concentrate on while designing a .NET application.


1. What is an Architecture Style?

An Architecture Style is the high-level blueprint that defines how different components of a system interact with each other. It sets the guidelines and principles for structuring your application.

Common Architecture Styles in .NET applications:

  1. Monolithic Architecture

    • Single, unified codebase.

    • Easier to build and deploy initially.

    • Hard to scale and maintain for large applications.

  2. Layered (N-Tier) Architecture

  3. Service-Oriented Architecture (SOA)

    • Applications are broken into services.

    • Often used with WCF or Web APIs in .NET.

  4. Microservices Architecture

    • Each service is independent and deployable.

    • Widely used with .NET 6+ and Azure Kubernetes Services (AKS).

    • Provides scalability and flexibility.

  5. Event-Driven Architecture

2. What are Architecture Patterns?

While Architecture Style defines the overall structure, Architecture Patterns provide practical implementation blueprints to solve common system-level challenges like scalability, fault tolerance, and maintainability.

Common Architecture Patterns in .NET:

  1. MVC (Model-View-Controller) Pattern

    • Widely used in ASP.NET MVC.

    • Separates UI, Business Logic, and Data.

  2. CQRS (Command Query Responsibility Segregation)

    • Separates read and write operations into different models.

    • Improves performance for large-scale .NET applications.

  3. Event Sourcing Pattern

    • Stores state changes as events rather than just the latest state.

    • Useful for audit trails and financial applications.

  4. Saga Pattern

    • Handles distributed transactions across multiple microservices.

    • Prevents data inconsistencies in .NET microservices.

  5. Onion/Hexagonal/Clean Architecture

    • Business rules at the core, external dependencies at the edge.

    • Popular in .NET Core Clean Architecture projects.

SEO Keywords: .NET Architecture Patterns, MVC in ASP.NET, CQRS in .NET, Clean Architecture with .NET Core.


3. What are Design Patterns?

Design Patterns are reusable solutions to common coding problems. Unlike architecture, design patterns operate at the code level. They make your application more flexible, testable, and easier to maintain.

Common Design Patterns in .NET:

  1. Singleton Pattern

    • Ensures only one instance of a class exists.

    • Used in caching, logging, and configuration management.

  2. Factory Pattern

    • Provides a way to create objects without exposing creation logic.

    • Useful in dependency injection with .NET Core DI container.

  3. Repository Pattern

  4. Decorator Pattern

    • Adds behavior to objects dynamically.

    • Example: Adding logging or caching around service methods.

  5. Observer Pattern

    • Notifies multiple subscribers about state changes.

    • Example: EventHandlers and Delegates in C#.

4. Major Areas to Concentrate While Designing a .NET Application

When building a robust .NET application architecture, here are the key focus areas:

  1. Scalability – Choose architecture styles (Microservices, Event-Driven) that allow horizontal scaling.

  2. Security – Implement authentication & authorization (OAuth2, JWT, Identity Server).

  3. Performance Optimization – Use caching (Redis, MemoryCache), optimize EF Core queries.

  4. Maintainability – Adopt Clean Architecture and SOLID principles.

  5. Resilience – Use retry policies, circuit breakers (Polly in .NET).

  6. DevOps and CI/CD – Integrate with Azure DevOps or GitHub Actions for automated deployment.

  7. Cloud-Native Design – Utilize Azure App Services, Azure Kubernetes, Azure SQL for hosting.

  8. Logging & Monitoring – Use Serilog, Application Insights, ELK Stack for observability.

SEO Keywords: Best Practices for .NET Application Design, .NET Clean Architecture, Scalable .NET Applications, Cloud-Native .NET Apps.


Conclusion

When designing a .NET application, understanding the differences between Architecture Styles, Architecture Patterns, and Design Patterns is crucial. Think of it like this:

  • Architecture Style = The overall city layout (Monolithic, Microservices).

  • Architecture Pattern = The specific building design blueprint (MVC, CQRS, Clean Architecture).

  • Design Pattern = The bricks and wiring inside the building (Singleton, Repository, Factory).

By focusing on scalability, security, performance, and maintainability, you can build enterprise-level .NET applications that stand the test of time.






Here’s a real-world .NET application architecture diagram showing how:
  • Architecture Styles (Monolithic, Microservices, Event-Driven) define the overall structure.
  • Architecture Patterns (MVC, CQRS, Clean Architecture, Saga) guide system-level implementations.
  • Design Patterns (Repository, Singleton, Factory, Observer) provide reusable coding solutions.




Thursday, September 25, 2025

Write a program to Generate all possible permutations of the string "ABCDEF" using C#.

 Since "ABCDEF" has 6 characters, the number of permutations = 6! = 720.

Here’s a C# program to generate and print them:

using System; using System.Collections.Generic; class Program { static void Main() { string input = "ABCDEF"; var results = new List<string>(); GetPermutations(input.ToCharArray(), 0, results); Console.WriteLine($"Total Permutations: {results.Count}"); foreach (var str in results) { Console.WriteLine(str); } } static void GetPermutations(char[] arr, int index, List<string> results) { if (index == arr.Length - 1) { results.Add(new string(arr)); } else { for (int i = index; i < arr.Length; i++) { Swap(ref arr[index], ref arr[i]); GetPermutations(arr, index + 1, results); Swap(ref arr[index], ref arr[i]); // backtrack } } } static void Swap(ref char a, ref char b) { if (a == b) return; char temp = a; a = b; b = temp; } }

🔹 How it works:

  1. Uses recursion to generate all permutations.

  2. Swaps characters, calls recursively, then backtracks to restore the original order.

  3. Stores all unique permutations in a List<string>.


🔹 Sample Output:

Total Permutations: 720 ABCDEF ABCDE F ABCDFE ABCEDF ... FEDCBA

👉 This prints all 720 unique permutations of "ABCDEF".

How to achieve state management in Angular — a complete guide with examples

 State is the data that drives your UI: user input, server data, UI selections, etc. In Angular apps — especially as they grow — managing that state so the UI stays predictable, testable, and fast becomes essential.

This article walks through practical approaches to state management in Angular, shows complete code examples (simple and advanced), and gives recommendations for which approach to use for different scenarios.

Table of contents

  1. What is application state and why manage it?

  2. Approaches at a glance

  3. Local component state (when it's enough)

  4. Parent–child communication with @Input / @Output

  5. Shared services + RxJS (recommended for small–medium apps) — complete todo example

  6. NgRx (Redux-style) for large apps — complete todo example (actions, reducer, selectors, effects)

  7. Comparison: choose the right approach

  8. Best practices & performance tips

  9. Testing tips

  10. Migration notes & closing


1 — What is application state and why manage it?

State = any data your UI depends on (lists, selected item, user session, forms, loading flags, etc.).
Why manage it explicitly?

  • Prevent inconsistent UI (two components disagreeing on the same data).

  • Make the app predictable and easier to debug/test.

  • Improve performance by avoiding unnecessary re-rendering.

  • Keep code maintainable as the app grows.


2 — Approaches at a glance

  • Local component state: simplest; use when state doesn't leave the component.

  • @Input / @Output: parent-child communication for small hierarchies.

  • Shared services + RxJS (BehaviorSubject / ReplaySubject): lightweight reactive store — great for many apps.

  • NgRx (Redux pattern): single immutable store with actions/reducers/selectors/effects — best for complex enterprise apps.

  • Other libraries: NGXS, Akita, or NgRx Component Store (for local feature stores).


3 — Local component state (when it's enough)

If your state only lives in one component, keep it there — simple and fast.

@Component({ selector: 'app-counter', template: ` < p > Count: { { count } }</ p > < button(click) = "increment()" > +</ button > ` }) export class CounterComponent { count = 0; increment() { this.count++; } }

Use this only when data doesn't need to be shared.


4 — Parent–child via @Input / @Output

Good for simple data flow between parent/child components. Avoid using this to pass data across many nesting levels.

// child.component.ts @Input() name!: string; @Output() saved = new EventEmitter<string>();

5 — Shared services + RxJS — the practical, reactive approach

When to use: medium apps, multiple components need the same data, or you want a reactive model without the complexity of NgRx.

Key idea

Create a singleton service that holds the state as RxJS streams (e.g., BehaviorSubject) and expose Observables to components. Components subscribe (usually via async pipe) and call service methods to update the state.

Example: Todo app using BehaviorSubject

Model

// todo.model.ts export interface Todo { id: number; title: string; completed: boolean; }

Service

// todo.service.ts import { Injectable } from '@angular/core'; import { BehaviorSubject, of } from 'rxjs'; import { delay } from 'rxjs/operators'; import { Todo } from './todo.model'; @Injectable({ providedIn: 'root' }) export class TodoService { private todosSubject = new BehaviorSubject<Todo[]>([]); readonly todos$ = this.todosSubject.asObservable(); private idCounter = 1; // Add a new todo add(title: string) { const newTodo: Todo = { id: this.idCounter++, title, completed: false }; this.todosSubject.next([...this.todosSubject.value, newTodo]); } // Toggle completed toggle(id: number) { const updated = this.todosSubject.value.map(t => t.id === id ? { ...t, completed: !t.completed } : t ); this.todosSubject.next(updated); } // Remove remove(id: number) { this.todosSubject.next(this.todosSubject.value.filter(t => t.id !== id)); } // Simulate loading from an API loadFromServer() { of<Todo[]>([ { id: 101, title: 'Use Angular', completed: false }, { id: 102, title: 'Write blog', completed: true } ]) .pipe(delay(800)) .subscribe(data => this.todosSubject.next(data)); } }

Component (presentation + binding)

// todo-list.component.ts import { Component, ChangeDetectionStrategy } from '@angular/core'; import { TodoService } from './todo.service'; @Component({ selector: 'app-todo-list', templateUrl: './todo-list.component.html', changeDetection: ChangeDetectionStrategy.OnPush // important for perf }) export class TodoListComponent { todos$ = this.todoService.todos$; newTitle = ''; constructor(private todoService: TodoService) {} add() { const title = this.newTitle.trim(); if (!title) return; this.todoService.add(title); this.newTitle = ''; } toggle(id: number) { this.todoService.toggle(id); } remove(id: number) { this.todoService.remove(id); } load() { this.todoService.loadFromServer(); } }

Template

<!-- todo-list.component.html --> <div> <input [(ngModel)]="newTitle" placeholder="New todo" /> <button (click)="add()">Add</button> <button (click)="load()">Load sample</button> </div> <ul> <li *ngFor="let todo of todos$ | async; trackBy: trackById"> <label> <input type="checkbox" [checked]="todo.completed" (change)="toggle(todo.id)" /> <span [class.completed]="todo.completed">{{ todo.title }}</span> </label> <button (click)="remove(todo.id)">Delete</button> </li> </ul>

Performance tips:

  • Use ChangeDetectionStrategy.OnPush.

  • Use trackBy in *ngFor.

  • Expose Observables and consume via async pipe — avoids manual subscriptions/unsubscriptions.

This pattern scales well for many apps, is easy to test, and keeps code readable.


6 — NgRx (Redux pattern) — for large, complex apps

When to use: large enterprise apps, many teams, complex UI flows, need time-travel debugging, want single source of truth.

NgRx implements a Redux-style architecture:

  • Actions — describe events (user or server).

  • Reducer — pure function that produces new state from previous state + action.

  • Store — holds the app state tree.

  • Selectors — memoized queries into the store.

  • Effects — handle side effects (HTTP, other async operations).

Below is a compact but full NgRx example for the same Todo functionality.

Actions

// todo.actions.ts import { createAction, props } from '@ngrx/store'; import { Todo } from './todo.model'; export const loadTodos = createAction('[Todo] Load Todos'); export const loadTodosSuccess = createAction('[Todo] Load Todos Success', props<{ todos: Todo[] }>()); export const loadTodosFailure = createAction('[Todo] Load Todos Failure', props<{ error: any }>()); export const addTodo = createAction('[Todo] Add', props<{ title: string }>()); export const toggleTodo = createAction('[Todo] Toggle', props<{ id: number }>()); export const removeTodo = createAction('[Todo] Remove', props<{ id: number }>());

State & Reducer

// todo.reducer.ts import { createReducer, on } from '@ngrx/store'; import * as TodoActions from './todo.actions'; import { Todo } from './todo.model'; export interface TodoState { todos: Todo[]; loading: boolean; error: string | null; } export const initialState: TodoState = { todos: [], loading: false, error: null }; export const todoReducer = createReducer( initialState, on(TodoActions.loadTodos, state => ({ ...state, loading: true })), on(TodoActions.loadTodosSuccess, (state, { todos }) => ({ ...state, loading: false, todos })), on(TodoActions.loadTodosFailure, (state, { error }) => ({ ...state, loading: false, error })), on(TodoActions.addTodo, (state, { title }) => { const newTodo: Todo = { id: Date.now(), title, completed: false }; return { ...state, todos: [...state.todos, newTodo] }; }), on(TodoActions.toggleTodo, (state, { id }) => ({ ...state, todos: state.todos.map(t => (t.id === id ? { ...t, completed: !t.completed } : t)) })), on(TodoActions.removeTodo, (state, { id }) => ({ ...state, todos: state.todos.filter(t => t.id !== id) })) );

Selectors

// todo.selectors.ts import { createFeatureSelector, createSelector } from '@ngrx/store'; import { TodoState } from './todo.reducer'; const selectTodoFeature = createFeatureSelector<TodoState>('todos'); export const selectAllTodos = createSelector(selectTodoFeature, s => s.todos); export const selectLoading = createSelector(selectTodoFeature, s => s.loading); export const selectError = createSelector(selectTodoFeature, s => s.error);

Effects (handle async loading)

// todo.effects.ts import { Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import * as TodoActions from './todo.actions'; import { HttpClient } from '@angular/common/http'; import { catchError, map, mergeMap, of } from 'rxjs'; @Injectable() export class TodoEffects { loadTodos$ = createEffect(() => this.actions$.pipe( ofType(TodoActions.loadTodos), mergeMap(() => this.http.get<any[]>('/api/todos').pipe( map(todos => TodoActions.loadTodosSuccess({ todos })), catchError(error => of(TodoActions.loadTodosFailure({ error }))) ) ) ) ); constructor(private actions$: Actions, private http: HttpClient) {} }

Module setup

// app.module.ts (imports snippet) import { StoreModule } from '@ngrx/store'; import { EffectsModule } from '@ngrx/effects'; import { todoReducer } from './state/todo.reducer'; import { TodoEffects } from './state/todo.effects'; import { StoreDevtoolsModule } from '@ngrx/store-devtools'; @NgModule({ imports: [ // ... StoreModule.forRoot({ todos: todoReducer }), EffectsModule.forRoot([TodoEffects]), StoreDevtoolsModule.instrument({ maxAge: 25 }) // optional, for debugging ], // ... }) export class AppModule {}

Component using the store

// todo-list.component.ts (NgRx version) export class TodoListComponent { todos$ = this.store.select(selectAllTodos); loading$ = this.store.select(selectLoading); constructor(private store: Store) {} ngOnInit() { this.store.dispatch(loadTodos()); } add(title: string) { this.store.dispatch(addTodo({ title })); } toggle(id: number) { this.store.dispatch(toggleTodo({ id })); } remove(id: number) { this.store.dispatch(removeTodo({ id })); } }

Notes:

  • NgRx encourages immutability and pure reducers.

  • Use selectors to memoize and compute derived data.

  • Effects are the recommended place for network calls and side effects.

  • NgRx has optional helpers like createEntityAdapter() for normalized collections.


7 — Comparison: which approach when?

ScenarioRecommended approach
Small component-only stateLocal component state
Parent-child simple sharing@Input / @Output
Many components, app-level shared state (small–medium)Service + RxJS (BehaviorSubject)
Complex app, many features, many teams, need strict patterns & devtoolsNgRx (or NGXS / Akita)
Feature-local store inside a component treeNgRx Component Store or Akita feature stores

8 — Best practices & performance tips

  • Prefer streams: Expose Observables from services and components; consume with async pipe.

  • OnPush change detection: Use for UI components showing Observable data.

  • Avoid storing derived state: Compute derived values with selectors or pipes.

  • Normalize big collections: Use entity adapters or normalized shapes (by id) to keep updates cheap.

  • Use trackBy with *ngFor.

  • Keep reducers pure and avoid side effects in reducers (use effects/services).

  • Lazy load feature state in NgRx to reduce initial bundle size.

  • Use devtools when using NgRx (@ngrx/store-devtools) for time-travel debugging.

  • Memoize selectors to avoid unnecessary recomputation.


9 — Testing tips

  • Services + BehaviorSubject: Test the service by subscribing to todos$ and asserting emissions after calling add()/remove()/toggle(). No need to mock Angular DI — the service is plain TS with RxJS.

  • NgRx:

    • Test reducers as pure functions (send action, assert new state).

    • Test effects with provideMockActions and mock HTTP calls.

    • Use provideMockStore for component unit tests to mock store selects and dispatches.

Example reducer test (Jasmine):

it('should add todo on addTodo action', () => { const initial = { todos: [], loading: false, error: null }; const action = addTodo({ title: 'New' }); const result = todoReducer(initial, action); expect(result.todos.length).toBe(1); expect(result.todos[0].title).toBe('New'); });

10 — Migration notes & closing

  • Start simple: adopt services + RxJS. Move to NgRx only when complexity/teams justify it.

  • Hybrid approach: many apps use a combination — NgRx for global domain state and services/component-store for local UI state.

  • Refactor incrementally: extract a feature into NgRx gradually (actions/reducer/selectors) instead of a big rewrite.


Final checklist for implementing state in Angular

  • Decide scope: local vs shared vs global.

  • Prefer immutable updates (spread operator).

  • Use Observables + async pipe.

  • Optimize change detection with OnPush.

  • Add unit tests for services, reducers, and effects.

  • For large apps, use NgRx (and devtools); for smaller apps, prefer services + RxJS.



🔹 .NET Framework vs .NET Core

 1. Introduction

  • .NET Framework

    • Released in 2002 by Microsoft.

    • Runs only on Windows.

    • Used for building desktop apps (WinForms, WPF), ASP.NET Web Forms/MVC, enterprise solutions, etc.

    • Mature and widely used, but Windows-only and no active feature development (only security fixes now).

  • .NET Core

    • Released in 2016 as a cross-platform, open-source re-implementation of .NET.

    • Runs on Windows, Linux, macOS.

    • Supports modern development (cloud, microservices, Docker, Kubernetes).

    • Actively developed and evolved into .NET 5+ (Unified .NET platform).


2. Key Differences

Feature.NET Framework.NET Core
Platform SupportWindows-onlyCross-platform (Windows, Linux, macOS)
DeploymentInstalled on Windows systemSelf-contained (bundled with app) or framework-dependent
PerformanceGood, but older runtimeHigh performance, optimized runtime (Kestrel web server)
Open SourceMostly closed sourceFully open source (on GitHub)
Application TypesWinForms, WPF, ASP.NET Web Forms/MVC, WCFASP.NET Core (MVC, Razor Pages, Blazor), Console, Microservices
Cross-Platform Development❌ No✅ Yes
Microservices SupportLimitedExcellent (Docker + Kubernetes ready)
Cloud Support (Azure, AWS)Supported but heavierOptimized for cloud-native
FutureMaintenance only (no major new features)Active development (merged into .NET 5, .NET 6, .NET 7, .NET 8, .NET 9 …)

3. Architecture Differences

.NET Framework:

  • Runs only with Windows OS + IIS (Internet Information Services) for hosting web apps.

  • Traditional monolithic applications.

.NET Core:

  • Uses Kestrel Web Server (lightweight, cross-platform).

  • Can be hosted in IIS, Nginx, Apache, or Docker containers.

  • Supports microservices architecture.


4. Performance

  • .NET Core apps are generally faster because of:

    • JIT (RyuJIT improvements)

    • Lightweight runtime

    • Optimized memory management

    • Kestrel (high-performance web server)

Example: ASP.NET Core can handle millions of requests/sec, while classic ASP.NET Framework is slower.


5. Deployment

  • .NET Framework: Installed on Windows; app depends on that system installation.

  • .NET Core:

    • Framework-dependent deployment (FDD): App runs on shared runtime.

    • Self-contained deployment (SCD): App carries its own runtime — no need to install .NET separately.


6. Ecosystem & Future

  • .NET Framework → Legacy projects, Windows desktop apps (still relevant for enterprises).

  • .NET Core → Foundation for modern apps.

  • In 2020, Microsoft merged everything into a unified platform called .NET 5+ (now we are at .NET 9).

    • .NET Framework will never get new features.

    • .NET Core is the future path.


7. When to Use

  • ✅ Use .NET Framework if:

    • You are maintaining an existing Windows-only enterprise app.

    • You need technologies not yet available in .NET Core (like full WCF or Web Forms).

  • ✅ Use .NET Core / .NET 5+ if:

    • You’re building new applications (web, microservices, cloud-native).

    • You want cross-platform support.

    • You care about performance and scalability.

    • You plan to use Docker, Kubernetes, or cloud hosting.


🔑 Quick Summary

  • .NET Framework = Old, Windows-only, legacy support.

  • .NET Core = Modern, cross-platform, high-performance, future-ready (part of .NET 5+).



Blog Archive

Don't Copy

Protected by Copyscape Online Plagiarism Checker

Pages