{"id":27019171,"url":"https://github.com/fullstackcodingguy/extr","last_synced_at":"2025-04-04T17:19:24.492Z","repository":{"id":279961977,"uuid":"940348269","full_name":"FullstackCodingGuy/ExTr","owner":"FullstackCodingGuy","description":"Personal Expense Tracker","archived":false,"fork":false,"pushed_at":"2025-03-17T10:50:54.000Z","size":27,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-17T11:38:57.204Z","etag":null,"topics":["api","authentication-middleware","backend","boilerplate-application","chatgpt","cors","entity-framework-core","expense-management","personal-expense-tracker","request-throttler","serilog","sqlite-database"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/FullstackCodingGuy.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2025-02-28T02:48:27.000Z","updated_at":"2025-03-17T10:50:58.000Z","dependencies_parsed_at":null,"dependency_job_id":"10a1df01-6888-42db-9761-bd2e27c7468c","html_url":"https://github.com/FullstackCodingGuy/ExTr","commit_stats":null,"previous_names":["fullstackcodingguy/extr"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FullstackCodingGuy%2FExTr","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FullstackCodingGuy%2FExTr/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FullstackCodingGuy%2FExTr/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FullstackCodingGuy%2FExTr/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/FullstackCodingGuy","download_url":"https://codeload.github.com/FullstackCodingGuy/ExTr/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247217214,"owners_count":20903009,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","authentication-middleware","backend","boilerplate-application","chatgpt","cors","entity-framework-core","expense-management","personal-expense-tracker","request-throttler","serilog","sqlite-database"],"created_at":"2025-04-04T17:19:23.801Z","updated_at":"2025-04-04T17:19:24.481Z","avatar_url":"https://github.com/FullstackCodingGuy.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 📌 Expense Tracker\n\n## 🚀 Overview\nThe **Expense Manager API** is a .NET 9-based application designed to track expenses efficiently. It follows **Clean Architecture** and adheres to **SOLID principles** for maintainability and scalability.\n\n---\n## 📌 Features\n✅ **CRUD Operations** (Add, Update, Retrieve, Delete Expenses)  \n✅ **Expense Categories**  \n✅ **SQLite Persistence**  \n✅ **Resilient API with Rate Limiting**  \n✅ **CORS Policy for Cross-Origin Requests**  \n✅ **Logging with Serilog**  \n✅ **Performance Optimizations**  \n✅ **Centralized Logging with SEQ (Optional)**  \n✅ **SAAS Enablement (all 3 strategies)**\n\n---\n## 🛠️ Tech Stack\n- **.NET 9** (Minimal API + Controllers)\n- **SQLite** (Persistent Storage)\n- **Entity Framework Core** (Database ORM)\n- **Serilog** (Logging)\n- **Rate Limiting** (Resiliency)\n\n---\n## 📌 Setup Instructions\n### 1️⃣ Clone Repository\n```sh\ngit clone \u003crepository-url\u003e\ncd ExpenseManager\n```\n\n### 2️⃣ Install Dependencies\n```sh\ndotnet restore\n```\n\n### 3️⃣ Apply Migrations \u0026 Initialize Database\n```sh\ndotnet ef migrations add InitialCreate\n\ndotnet ef database update\n```\n\n### 4️⃣ Run the API\n```sh\ndotnet run\n```\nThe API will be available at **`http://localhost:5000`**\n\n---\n\n### Run the API using Docker\n```\nmake dockerrun\n\nor \n\ndocker run -d -p 8080:8080 --name expense-api-container expense-api\n\nThe API will now be accessible at: http://localhost:8080\n\n```\n\n---\n## 📌 SQLite Persistence\nThe API uses **SQLite** for data persistence. The database file (`expense.db`) is automatically created upon running the migrations.\n\nIf needed, delete `expense.db` and reapply migrations:\n```sh\nrm expense.db\n\ndotnet ef database update\n```\n\n---\n## 📌 Serilog Setup for Logging\nSerilog is configured to log messages to **console, files, and SEQ**.\n### 1️⃣ Install Serilog Packages\n```sh\ndotnet add package Serilog.AspNetCore\ndotnet add package Serilog.Sinks.Console\ndotnet add package Serilog.Sinks.File\ndotnet add package Serilog.Sinks.Seq\n```\n\n### 2️⃣ Configure Logging in `Program.cs`\n```csharp\nLog.Logger = new LoggerConfiguration()\n    .WriteTo.Console()\n    .WriteTo.File(\"logs/api-log.txt\", rollingInterval: RollingInterval.Day)\n    .WriteTo.Seq(\"http://localhost:5341\")\n    .Enrich.FromLogContext()\n    .MinimumLevel.Information()\n    .CreateLogger();\n\nbuilder.Host.UseSerilog();\n```\n\n### 3️⃣ Run SEQ for Centralized Logging\nIf you want to enable **centralized logging**, start SEQ using Docker:\n```sh\ndocker run --name seq -d -e ACCEPT_EULA=Y -p 5341:80 datalust/seq\n```\nThen, open **http://localhost:5341** to view logs.\n\n---\n## 📌 Implemented API Endpoints\n| Method | Endpoint           | Description       |\n|--------|-------------------|-------------------|\n| GET    | `/api/expenses`   | Retrieve expenses |\n| POST   | `/api/expenses`   | Add a new expense |\n| PUT    | `/api/expenses/{id}` | Update an expense |\n| DELETE | `/api/expenses/{id}` | Delete an expense |\n\n---\n## 📌 API Resilience - Rate Limiting\nTo prevent **API abuse**, the following **rate limits** are applied:\n- **Max 100 requests per 10 minutes per IP**\n\nImplemented in `Program.cs`:\n```csharp\nbuilder.Services.AddRateLimiter(options =\u003e\n{\n    options.AddFixedWindowLimiter(\"fixed\", policy =\u003e policy\n        .PermitLimit(100)\n        .Window(TimeSpan.FromMinutes(10)));\n});\n```\n\n---\n## 📌 CORS Policy\nTo allow **cross-origin requests**, CORS is enabled:\n```csharp\nbuilder.Services.AddCors(options =\u003e\n{\n    options.AddPolicy(\"AllowAll\", policy =\u003e policy\n        .AllowAnyOrigin()\n        .AllowAnyMethod()\n        .AllowAnyHeader());\n});\n```\n\nActivate in the middleware:\n```csharp\napp.UseCors(\"AllowAll\");\n```\n\n---\n## 📌 Performance Optimizations\n### 🔹 Response Compression\nEnable **Gzip/Brotli compression** for faster API responses:\n```csharp\nbuilder.Services.AddResponseCompression(options =\u003e\n{\n    options.EnableForHttps = true;\n});\napp.UseResponseCompression();\n```\n\n### 🔹 Database Connection Pooling\nOptimize database access using **connection pooling**:\n```csharp\nbuilder.Services.AddDbContextPool\u003cExpenseDbContext\u003e(options =\u003e\n    options.UseSqlite(builder.Configuration.GetConnectionString(\"DefaultConnection\")));\n```\n\n---\n## 📌 Best Practices Implemented\n✅ **SOLID Principles** - Clean Architecture \u0026 Decoupled Design  \n✅ **Dependency Injection** - Proper Service Layer Usage  \n✅ **Rate Limiting** - API Protection  \n✅ **Structured Logging** - Serilog + SEQ for Monitoring  \n✅ **CORS Policy** - Secure Cross-Origin Access  \n✅ **Performance Tweaks** - Response Compression, DB Connection Pooling  \n\n------\n\n### Implementing design patterns in this solution\n\u003cdetails\u003e\n\u003csummary\u003eread\u003c/summary\u003e\n\n\n\nFor your **Expense Manager API**, here are some **design patterns** that can be implemented to improve **maintainability, scalability, and testability** while adhering to **SOLID principles**:\n\n---\n\n### 🔹 **1️⃣ Repository Pattern** (Already Implemented)\n- **Use Case:** Abstracts database operations, making the API **decoupled** from the persistence logic.\n- **Implementation:** The `IExpenseRepository` interface ensures **loose coupling**, and concrete implementations interact with **SQLite via EF Core**.\n\n```csharp\npublic interface IExpenseRepository\n{\n    Task\u003cIEnumerable\u003cExpense\u003e\u003e GetAllAsync();\n    Task\u003cExpense\u003e GetByIdAsync(int id);\n    Task AddAsync(Expense expense);\n    Task UpdateAsync(Expense expense);\n    Task DeleteAsync(int id);\n}\n```\n\n✅ **Benefits:**  \n✔ Encapsulates database logic  \n✔ Makes the app easier to switch databases (e.g., SQL Server, PostgreSQL)  \n\n---\n\n### 🔹 **2️⃣ Unit of Work Pattern** (For Transactional Consistency)\n- **Use Case:** When multiple database operations need to be committed together **(e.g., adding an expense + logging the action)**.\n- **Implementation:** Wrap repository operations in a **single transaction**.\n\n```csharp\npublic interface IUnitOfWork\n{\n    IExpenseRepository Expenses { get; }\n    Task\u003cint\u003e SaveChangesAsync();\n}\n```\n✅ **Benefits:**  \n✔ Ensures consistency across multiple database operations  \n✔ Reduces unnecessary database calls  \n\n---\n\n### 🔹 **3️⃣ Factory Pattern** (For Expense Object Creation)\n- **Use Case:** If there are **different types of expenses** (e.g., **Personal, Business, Investment**) and creation logic varies.\n- **Implementation:** Use a **factory** to instantiate different expense objects.\n\n```csharp\npublic static class ExpenseFactory\n{\n    public static Expense CreateExpense(string type, string title, decimal amount)\n    {\n        return type switch\n        {\n            \"Personal\" =\u003e new PersonalExpense(title, amount),\n            \"Business\" =\u003e new BusinessExpense(title, amount),\n            _ =\u003e throw new ArgumentException(\"Invalid Expense Type\")\n        };\n    }\n}\n```\n\n✅ **Benefits:**  \n✔ Encapsulates object creation logic  \n✔ Easier to introduce new expense types  \n\n---\n\n### 🔹 **4️⃣ Strategy Pattern** (For Expense Categorization Logic)\n- **Use Case:** If **expense categorization rules** change frequently.\n- **Implementation:** Define **multiple categorization strategies** dynamically.\n\n```csharp\npublic interface IExpenseCategorizationStrategy\n{\n    string Categorize(Expense expense);\n}\n\npublic class AmountBasedCategorization : IExpenseCategorizationStrategy\n{\n    public string Categorize(Expense expense)\n    {\n        return expense.Amount \u003e 1000 ? \"High\" : \"Low\";\n    }\n}\n```\n\n✅ **Benefits:**  \n✔ Makes the categorization logic **flexible \u0026 interchangeable**  \n✔ Avoids **if-else** clutter in the business logic  \n\n---\n\n### 🔹 **5️⃣ CQRS (Command Query Responsibility Segregation)**\n- **Use Case:** If the application needs to scale by **separating read and write operations** (e.g., expensive analytics queries).\n- **Implementation:** Define **separate** commands (writes) and queries (reads).\n\n```csharp\npublic record AddExpenseCommand(string Title, decimal Amount);\npublic record GetExpenseQuery(int Id);\n```\n\n✅ **Benefits:**  \n✔ Improves **scalability** when using **read replicas**  \n✔ Optimizes performance for complex queries  \n\n---\n\n### 🔹 **6️⃣ Decorator Pattern** (For Adding Extra Features Dynamically)\n- **Use Case:** If you need to **add logging, caching, or validation** without modifying the core repository logic.\n- **Implementation:** Decorate `IExpenseRepository` with **logging functionality**.\n\n```csharp\npublic class ExpenseRepositoryLoggingDecorator : IExpenseRepository\n{\n    private readonly IExpenseRepository _inner;\n    private readonly ILogger\u003cExpenseRepositoryLoggingDecorator\u003e _logger;\n\n    public ExpenseRepositoryLoggingDecorator(IExpenseRepository inner, ILogger\u003cExpenseRepositoryLoggingDecorator\u003e logger)\n    {\n        _inner = inner;\n        _logger = logger;\n    }\n\n    public async Task\u003cIEnumerable\u003cExpense\u003e\u003e GetAllAsync()\n    {\n        _logger.LogInformation(\"Fetching all expenses.\");\n        return await _inner.GetAllAsync();\n    }\n}\n```\n\n✅ **Benefits:**  \n✔ Adds **cross-cutting concerns** (e.g., logging) without modifying existing code  \n✔ Follows **Open/Closed Principle (OCP)**  \n\n---\n\n### 🔹 **7️⃣ Chain of Responsibility Pattern** (For Expense Validation)\n- **Use Case:** If multiple **validation steps** need to be executed **sequentially**.\n- **Implementation:** Define **linked handlers** for different validation steps.\n\n```csharp\npublic abstract class ExpenseValidationHandler\n{\n    protected ExpenseValidationHandler? Next;\n\n    public void SetNext(ExpenseValidationHandler next) =\u003e Next = next;\n\n    public abstract void Handle(Expense expense);\n}\n\npublic class AmountValidationHandler : ExpenseValidationHandler\n{\n    public override void Handle(Expense expense)\n    {\n        if (expense.Amount \u003c= 0)\n            throw new Exception(\"Amount must be greater than zero.\");\n\n        Next?.Handle(expense);\n    }\n}\n```\n\n✅ **Benefits:**  \n✔ Makes the validation **modular** and **extensible**  \n✔ Avoids a **massive if-else block**  \n\n---\n\n### 🔹 **8️⃣ Observer Pattern** (For Notifications)\n- **Use Case:** If **other services need to react** when an expense is added (e.g., **send email notification**).\n- **Implementation:** Use an **event-driven approach**.\n\n```csharp\npublic class ExpenseNotifier\n{\n    private readonly List\u003cIObserver\u003e _observers = new();\n\n    public void Subscribe(IObserver observer) =\u003e _observers.Add(observer);\n    public void Notify(Expense expense) =\u003e _observers.ForEach(o =\u003e o.Update(expense));\n}\n```\n\n✅ **Benefits:**  \n✔ Allows **event-driven behavior** (e.g., notifying services)  \n✔ Enhances **scalability** without modifying the core logic  \n\n---\n\n### 🎯 **Final Thoughts**\n| **Pattern** | **Use Case** | **Benefit** |\n|------------|-------------|------------|\n| **Repository** | Encapsulate database operations | Decouples persistence from business logic |\n| **Unit of Work** | Handle transactions | Ensures atomicity \u0026 consistency |\n| **Factory** | Create different types of expenses | Centralizes object creation logic |\n| **Strategy** | Dynamic expense categorization | Avoids complex if-else logic |\n| **CQRS** | Separate read \u0026 write operations | Enhances scalability |\n| **Decorator** | Add logging, caching | Extends behavior without modifying core logic |\n| **Chain of Responsibility** | Expense validation | Modular validation steps |\n| **Observer** | Notify other services | Enables event-driven architecture |\n\n---\n\n\n\u003c/details\u003e\n\n### Applying Decorator Pattern\n\n\u003cdetails\u003e\n\u003csummary\u003eread\u003c/summary\u003e\n\n\nThe **Decorator Pattern** is beneficial in the **Expense Manager API** when you need to **extend the behavior of existing services without modifying them directly**. In this use case, it can help with:  \n\n### 🔹 **Where Can We Use the Decorator Pattern?**\n1. **Logging Decorator** – Log every expense-related operation.  \n2. **Caching Decorator** – Cache frequent read operations (e.g., fetching expenses).  \n3. **Validation Decorator** – Add validation rules dynamically.  \n4. **Security/Authorization Decorator** – Check user roles before executing a request.  \n5. **Transaction Decorator** – Ensure database consistency.  \n\n---\n\n## **1️⃣ Logging Decorator** (Example)  \nInstead of adding logging directly into `ExpenseRepository`, we **wrap** it in a decorator.  \n\n### ✅ **Implementation**\n```csharp\npublic class ExpenseRepositoryLoggingDecorator : IExpenseRepository\n{\n    private readonly IExpenseRepository _inner;\n    private readonly ILogger\u003cExpenseRepositoryLoggingDecorator\u003e _logger;\n\n    public ExpenseRepositoryLoggingDecorator(IExpenseRepository inner, ILogger\u003cExpenseRepositoryLoggingDecorator\u003e logger)\n    {\n        _inner = inner;\n        _logger = logger;\n    }\n\n    public async Task\u003cIEnumerable\u003cExpense\u003e\u003e GetAllAsync()\n    {\n        _logger.LogInformation(\"Fetching all expenses.\");\n        var result = await _inner.GetAllAsync();\n        _logger.LogInformation($\"Retrieved {result.Count()} expenses.\");\n        return result;\n    }\n\n    public async Task\u003cExpense\u003e GetByIdAsync(int id)\n    {\n        _logger.LogInformation($\"Fetching expense with ID {id}.\");\n        return await _inner.GetByIdAsync(id);\n    }\n\n    public async Task AddAsync(Expense expense)\n    {\n        _logger.LogInformation($\"Adding expense: {expense.Title}, Amount: {expense.Amount}\");\n        await _inner.AddAsync(expense);\n        _logger.LogInformation(\"Expense added successfully.\");\n    }\n\n    public async Task UpdateAsync(Expense expense)\n    {\n        _logger.LogInformation($\"Updating expense ID {expense.Id}.\");\n        await _inner.UpdateAsync(expense);\n        _logger.LogInformation(\"Expense updated successfully.\");\n    }\n\n    public async Task DeleteAsync(int id)\n    {\n        _logger.LogInformation($\"Deleting expense ID {id}.\");\n        await _inner.DeleteAsync(id);\n        _logger.LogInformation(\"Expense deleted successfully.\");\n    }\n}\n```\n### ✅ **Usage**\nRegister the decorated repository in **DI container** in `Program.cs`:\n```csharp\nbuilder.Services.AddScoped\u003cIExpenseRepository, ExpenseRepository\u003e();\nbuilder.Services.Decorate\u003cIExpenseRepository, ExpenseRepositoryLoggingDecorator\u003e();\n```\n\n📌 **Benefits:**  \n✔ **Non-intrusive logging** (no need to modify `ExpenseRepository`)  \n✔ **Extensible** (easily add more behaviors)  \n✔ **Follows Open/Closed Principle** (OCP)  \n\n---\n\n## **2️⃣ Caching Decorator**  \nReduces **database queries** by caching expenses.\n\n### ✅ **Implementation**\n```csharp\npublic class ExpenseRepositoryCachingDecorator : IExpenseRepository\n{\n    private readonly IExpenseRepository _inner;\n    private readonly IMemoryCache _cache;\n\n    public ExpenseRepositoryCachingDecorator(IExpenseRepository inner, IMemoryCache cache)\n    {\n        _inner = inner;\n        _cache = cache;\n    }\n\n    public async Task\u003cIEnumerable\u003cExpense\u003e\u003e GetAllAsync()\n    {\n        return await _cache.GetOrCreateAsync(\"expenses_cache\", async entry =\u003e\n        {\n            entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5);\n            return await _inner.GetAllAsync();\n        });\n    }\n\n    public async Task\u003cExpense\u003e GetByIdAsync(int id)\n    {\n        return await _cache.GetOrCreateAsync($\"expense_{id}\", async entry =\u003e\n        {\n            entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5);\n            return await _inner.GetByIdAsync(id);\n        });\n    }\n\n    public async Task AddAsync(Expense expense)\n    {\n        await _inner.AddAsync(expense);\n        _cache.Remove(\"expenses_cache\");\n    }\n\n    public async Task UpdateAsync(Expense expense)\n    {\n        await _inner.UpdateAsync(expense);\n        _cache.Remove($\"expense_{expense.Id}\");\n    }\n\n    public async Task DeleteAsync(int id)\n    {\n        await _inner.DeleteAsync(id);\n        _cache.Remove($\"expense_{id}\");\n    }\n}\n```\n### ✅ **Usage**\n```csharp\nbuilder.Services.AddScoped\u003cIExpenseRepository, ExpenseRepository\u003e();\nbuilder.Services.Decorate\u003cIExpenseRepository, ExpenseRepositoryCachingDecorator\u003e();\n```\n📌 **Benefits:**  \n✔ Reduces **database load**  \n✔ Improves **API performance**  \n✔ Follows **OCP** (Open/Closed Principle)  \n\n---\n\n## **3️⃣ Validation Decorator**\nEnsures **expenses have valid data** before persisting.\n\n### ✅ **Implementation**\n```csharp\npublic class ExpenseRepositoryValidationDecorator : IExpenseRepository\n{\n    private readonly IExpenseRepository _inner;\n\n    public ExpenseRepositoryValidationDecorator(IExpenseRepository inner)\n    {\n        _inner = inner;\n    }\n\n    public async Task AddAsync(Expense expense)\n    {\n        if (string.IsNullOrEmpty(expense.Title))\n            throw new ArgumentException(\"Title is required.\");\n        if (expense.Amount \u003c= 0)\n            throw new ArgumentException(\"Amount must be greater than zero.\");\n\n        await _inner.AddAsync(expense);\n    }\n\n    public async Task\u003cIEnumerable\u003cExpense\u003e\u003e GetAllAsync() =\u003e await _inner.GetAllAsync();\n    public async Task\u003cExpense\u003e GetByIdAsync(int id) =\u003e await _inner.GetByIdAsync(id);\n    public async Task UpdateAsync(Expense expense) =\u003e await _inner.UpdateAsync(expense);\n    public async Task DeleteAsync(int id) =\u003e await _inner.DeleteAsync(id);\n}\n```\n### ✅ **Usage**\n```csharp\nbuilder.Services.AddScoped\u003cIExpenseRepository, ExpenseRepository\u003e();\nbuilder.Services.Decorate\u003cIExpenseRepository, ExpenseRepositoryValidationDecorator\u003e();\n```\n📌 **Benefits:**  \n✔ **Keeps validation separate** from repository logic  \n✔ **Prevents invalid data from entering the database**  \n\n---\n\n## **🎯 Why Use Decorator Pattern?**\n| **Pattern**   | **Use Case**                     | **Benefit**                         |\n|--------------|--------------------------------|----------------------------------|\n| **Logging**   | Log repository actions         | Non-intrusive, structured logs  |\n| **Caching**   | Reduce DB calls for reads      | Improves performance            |\n| **Validation** | Validate expenses before saving | Keeps concerns separate          |\n\n📌 **Best Part?** ✅ You can **stack multiple decorators together!**  \nExample:\n```csharp\nbuilder.Services.AddScoped\u003cIExpenseRepository, ExpenseRepository\u003e();\nbuilder.Services.Decorate\u003cIExpenseRepository, ExpenseRepositoryValidationDecorator\u003e();\nbuilder.Services.Decorate\u003cIExpenseRepository, ExpenseRepositoryCachingDecorator\u003e();\nbuilder.Services.Decorate\u003cIExpenseRepository, ExpenseRepositoryLoggingDecorator\u003e();\n```\n✔ **Validation → Caching → Logging** in order 🔄  \n\n---\n\n### **🚀 Summary**\n🔹 The **Decorator Pattern** helps in **adding cross-cutting concerns dynamically**.  \n🔹 **No need to modify core repository code** – just wrap and extend!  \n🔹 Follows **OCP (Open/Closed Principle)** – Code is **open for extension, closed for modification**.  \n🔹 Improves **maintainability \u0026 testability**.  \n\nWould you like help implementing **unit tests** for these decorators? 🚀\n\n\u003c/details\u003e\n\n---\n\n## Implementing Authentication\n\n- https://github.com/FullstackCodingGuy/Developer-Fundamentals/wiki/Authentication#implementing-keycloak-in-a-net-9-api-solution\n\n---\n## 📌 License\nThis project is open-source and available under the **MIT License**.\n\n---\n## 🎯 **Final Notes**\nThis API is production-ready, **resilient**, and **scalable**. Feel free to customize it for your needs!\n\n💬 **Need Help?** Reach out for support! 🚀\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffullstackcodingguy%2Fextr","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffullstackcodingguy%2Fextr","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffullstackcodingguy%2Fextr/lists"}