Skip to content

Commit ef8a8fe

Browse files
committed
Fix README file
1 parent 7b845d9 commit ef8a8fe

File tree

1 file changed

+78
-173
lines changed

1 file changed

+78
-173
lines changed

README.md

Lines changed: 78 additions & 173 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
# KSFramework
22

3-
**KSFramework** is a lightweight, extensible .NET framework designed to accelerate clean architecture and domain-driven development. It offers generic repository support, pagination utilities, and helpful abstractions to simplify the implementation of enterprise-grade backend systems using modern .NET patterns.
3+
**KSFramework** is a modular and extensible .NET framework that simplifies the implementation of enterprise-grade applications using **Clean Architecture**, **DDD**, and **CQRS** patterns. It includes built-in support for Generic Repository, Unit of Work, and a custom MediatR-style messaging system.
44

55
---
66

77
## ✨ Features
88

9-
- 🧱 **Domain-Driven Design (DDD) Friendly**
10-
- 🧼 **Clean Architecture Support**
11-
- 🧰 **Generic Repository Pattern**
12-
- 📄 **Built-in Pagination Support**
13-
- 🧪 **Unit Testable Core Interfaces**
14-
- 🧩 **Customizable and Extensible by Design**
9+
- ✅ Clean Architecture and DDD-friendly structure
10+
- ✅ Generic Repository with constraint on AggregateRoots
11+
- ✅ Fully extensible Unit of Work pattern
12+
- ✅ Built-in Pagination utilities
13+
- ✅ Internal MediatR-like message dispatch system (KSMessaging)
14+
- ✅ Scrutor-based handler and behavior registration
15+
- ✅ Support for pipeline behaviors (logging, validation, etc.)
16+
- ✅ Strongly testable, loosely coupled abstractions
1517

1618
---
1719

@@ -23,267 +25,170 @@ Install via NuGet:
2325
dotnet add package KSFramework
2426
```
2527

26-
Or via the Package Manager Console:
27-
28-
```powershell
29-
Install-Package KSFramework
30-
```
31-
3228
---
3329

34-
## 📂 Project Structure
30+
## 🧰 Modules Overview
3531

36-
The KSFramework package is modular and consists of:
37-
38-
- `KSFramework.GenericRepository` — Contains generic repository interfaces and base implementations.
39-
- `KSFramework.Pagination` — Provides interfaces and classes for paginated queries.
40-
- `KSFramework.KSDomain` — Base types for entities, aggregate roots, and value objects.
32+
- `KSFramework.KSDomain` — Domain primitives: `Entity`, `AggregateRoot`, `ValueObject`
33+
- `KSFramework.GenericRepository``Repository`, `UnitOfWork`, pagination support
34+
- `KSFramework.KSMessaging` — CQRS with internal MediatR-style handler resolver, behaviors, stream handling
4135

4236
---
4337

44-
## 🚀 Getting Started
38+
## ⚙️ Usage Overview
4539

46-
### 1. Define Your Entities
40+
### 🧱 Register Services (Program.cs)
4741

4842
```csharp
49-
public class BlogPost
50-
{
51-
public int Id { get; set; }
52-
public string Title { get; set; }
53-
public string Content { get; set; }
54-
public DateTime PublishedAt { get; set; }
55-
}
43+
builder.Services.AddKSMediator(typeof(Program).Assembly);
44+
builder.Services.AddScoped<IUnitOfWork, UnitOfWork>();
5645
```
5746

58-
### 2. Setup Your DbContext
47+
### 🧪 Sample Command
5948

6049
```csharp
61-
public class AppDbContext : DbContext
62-
{
63-
public DbSet<BlogPost> BlogPosts { get; set; }
50+
public record CreatePostCommand(string Title, string Content) : ICommand<Guid>;
6451

65-
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
66-
}
67-
```
68-
69-
### 3. Create Your Repository
70-
71-
You can use the generic repository directly:
72-
73-
```csharp
74-
public class BlogPostRepository : Repository<BlogPost>
52+
public class CreatePostHandler : ICommandHandler<CreatePostCommand, Guid>
7553
{
76-
public BlogPostRepository(AppDbContext context) : base(context) { }
77-
}
78-
```
79-
80-
Or inject `IRepository<BlogPost>` if you're using dependency injection with Scrutor or your own DI container.
54+
private readonly IUnitOfWork _uow;
8155

82-
---
83-
84-
## 🧪 Common Repository Operations
85-
86-
```csharp
87-
await _repository.AddAsync(new BlogPost { Title = "Welcome", Content = "This is the first post." });
88-
89-
var post = await _repository.GetByIdAsync(1);
90-
91-
await _repository.UpdateAsync(post);
92-
93-
await _repository.DeleteAsync(post);
94-
95-
var allPosts = await _repository.GetAllAsync();
96-
97-
var filtered = _repository.Find(p => p.PublishedAt > DateTime.UtcNow.AddDays(-30));
98-
```
99-
100-
---
101-
102-
## 📘 Pagination Example
56+
public CreatePostHandler(IUnitOfWork uow)
57+
{
58+
_uow = uow;
59+
}
10360

104-
```csharp
105-
var query = _repository.Query();
106-
var paginated = await query.PaginateAsync(pageNumber: 1, pageSize: 10);
61+
public async Task<Guid> Handle(CreatePostCommand request, CancellationToken cancellationToken)
62+
{
63+
var post = new BlogPost { Id = Guid.NewGuid(), Title = request.Title, Content = request.Content };
64+
await _uow.GetRepository<BlogPost>().AddAsync(post);
65+
await _uow.SaveChangesAsync();
66+
return post.Id;
67+
}
68+
}
10769
```
10870

109-
`PaginateAsync` is an extension method provided by the `KSFramework.Pagination` namespace.
110-
11171
---
11272

113-
## 📚 Full Example: Building a Blog with Weekly Newsletter
114-
115-
### Project Overview
73+
## 📚 Example: Blog with Newsletter
11674

117-
This sample demonstrates how to build a simple blog platform using `KSFramework` with:
75+
### ✍️ Features
11876

119-
- Post publishing
120-
- Subscriber registration
121-
- Weekly newsletter delivery to subscribers
77+
- CRUD for Blog Posts using Commands/Queries
78+
- Subscriber registration using Notification pattern
79+
- Weekly newsletter dispatch via background service
12280

12381
---
12482

125-
### Step 1: Define Entities
83+
### 🧩 Domain Layer
12684

12785
```csharp
128-
public class BlogPost
86+
public class BlogPost : AggregateRoot<Guid>
12987
{
130-
public int Id { get; set; }
13188
public string Title { get; set; }
13289
public string Content { get; set; }
13390
public DateTime PublishedAt { get; set; }
13491
}
13592

136-
public class Subscriber
93+
public class Subscriber : Entity<Guid>
13794
{
138-
public int Id { get; set; }
13995
public string Email { get; set; }
14096
}
14197
```
14298

14399
---
144100

145-
### Step 2: Repositories
101+
### 🧠 Application Commands
146102

147103
```csharp
148-
public class BlogPostRepository : Repository<BlogPost>
149-
{
150-
public BlogPostRepository(AppDbContext context) : base(context) { }
151-
}
104+
public record SubscribeCommand(string Email) : ICommand;
152105

153-
public class SubscriberRepository : Repository<Subscriber>
106+
public class SubscribeHandler : ICommandHandler<SubscribeCommand>
154107
{
155-
public SubscriberRepository(AppDbContext context) : base(context) { }
156-
}
157-
```
108+
private readonly IUnitOfWork _uow;
158109

159-
---
110+
public SubscribeHandler(IUnitOfWork uow) => _uow = uow;
160111

161-
### Step 3: API Controllers
162-
163-
```csharp
164-
[ApiController]
165-
[Route("api/[controller]")]
166-
public class BlogPostsController : ControllerBase
167-
{
168-
private readonly IRepository<BlogPost> _repository;
169-
170-
public BlogPostsController(IRepository<BlogPost> repository)
112+
public async Task Handle(SubscribeCommand request, CancellationToken cancellationToken)
171113
{
172-
_repository = repository;
173-
}
174-
175-
[HttpGet]
176-
public async Task<IEnumerable<BlogPost>> GetAll() => await _repository.GetAllAsync();
177-
178-
[HttpPost]
179-
public async Task<IActionResult> Create(BlogPost post)
180-
{
181-
post.PublishedAt = DateTime.UtcNow;
182-
await _repository.AddAsync(post);
183-
return Ok(post);
114+
var repo = _uow.GetRepository<Subscriber>();
115+
await repo.AddAsync(new Subscriber { Email = request.Email });
116+
await _uow.SaveChangesAsync();
184117
}
185118
}
186119
```
187120

188121
---
189122

190-
### Step 4: Subscription Controller
123+
### 🌐 API Controller
191124

192125
```csharp
193126
[ApiController]
194127
[Route("api/[controller]")]
195128
public class NewsletterController : ControllerBase
196129
{
197-
private readonly IRepository<Subscriber> _subRepo;
130+
private readonly ISender _sender;
198131

199-
public NewsletterController(IRepository<Subscriber> subRepo)
200-
{
201-
_subRepo = subRepo;
202-
}
132+
public NewsletterController(ISender sender) => _sender = sender;
203133

204134
[HttpPost("subscribe")]
205-
public async Task<IActionResult> Subscribe(string email)
135+
public async Task<IActionResult> Subscribe([FromForm] string email)
206136
{
207-
await _subRepo.AddAsync(new Subscriber { Email = email });
137+
await _sender.Send(new SubscribeCommand(email));
208138
return Ok("Subscribed");
209139
}
210140
}
211141
```
212142

213143
---
214144

215-
### Step 5: Weekly Newsletter Job (Example)
216-
217-
You can use [Hangfire](https://www.hangfire.io/) or any scheduler to run this task weekly:
145+
### 📨 Weekly Newsletter Job
218146

219147
```csharp
220148
public class WeeklyNewsletterJob
221149
{
222-
private readonly IRepository<Subscriber> _subRepo;
223-
private readonly IRepository<BlogPost> _postRepo;
150+
private readonly IUnitOfWork _uow;
224151

225-
public WeeklyNewsletterJob(IRepository<Subscriber> subRepo, IRepository<BlogPost> postRepo)
226-
{
227-
_subRepo = subRepo;
228-
_postRepo = postRepo;
229-
}
152+
public WeeklyNewsletterJob(IUnitOfWork uow) => _uow = uow;
230153

231-
public async Task Send()
154+
public async Task SendAsync()
232155
{
233-
var lastWeek = DateTime.UtcNow.AddDays(-7);
234-
var posts = (await _postRepo.GetAllAsync())
235-
.Where(p => p.PublishedAt > lastWeek)
236-
.ToList();
156+
var posts = (await _uow.GetRepository<BlogPost>().GetAllAsync())
157+
.Where(p => p.PublishedAt >= DateTime.UtcNow.AddDays(-7));
237158

238-
var subscribers = await _subRepo.GetAllAsync();
159+
var subscribers = await _uow.GetRepository<Subscriber>().GetAllAsync();
239160

240-
foreach (var sub in subscribers)
161+
foreach (var s in subscribers)
241162
{
242-
// send email (via SMTP or 3rd party)
243-
await EmailSender.Send(sub.Email, "Weekly Newsletter", RenderPosts(posts));
244-
}
245-
}
246-
247-
private string RenderPosts(IEnumerable<BlogPost> posts)
248-
{
249-
return string.Join("
163+
await EmailSender.Send(s.Email, "Your Weekly Digest", string.Join("
250164
251-
", posts.Select(p => $"{p.Title}
252-
{p.Content}"));
165+
", posts.Select(p => p.Title)));
166+
}
253167
}
254168
}
255169
```
256170

257171
---
258172

259-
## 📌 Roadmap
173+
## 🧪 Testing
260174

261-
- [x] Generic Repository
262-
- [x] Pagination
263-
- [ ] Specification Pattern
264-
- [ ] Auditing Support
265-
- [ ] Integration with MediatR & CQRS
175+
- Handlers and pipelines are fully testable using unit tests
176+
- Use `KSFramework.UnitTests` project to validate handler behavior
177+
- Custom behaviors can be tested independently
266178

267179
---
268180

269-
## 🤝 Contributing
270-
271-
Contributions are welcome!
181+
## 📌 Roadmap
272182

273-
1. Fork the repository
274-
2. Create a feature branch
275-
3. Submit a pull request
183+
- [x] Internal MediatR/CQRS support
184+
- [x] Pagination
185+
- [x] Unit of Work abstraction
186+
- [ ] ValidationBehavior
187+
- [ ] ExceptionHandlingBehavior
188+
- [ ] Domain Events integration
276189

277190
---
278191

279192
## 📄 License
280193

281-
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
282-
283-
---
284-
285-
## 🔗 Related Projects
286-
287-
- [MediatR](https://github.com/jbogard/MediatR)
288-
- [Scrutor](https://github.com/khellang/Scrutor)
289-
- [Hangfire](https://www.hangfire.io/)
194+
Licensed under MIT.

0 commit comments

Comments
 (0)