DEV Community

Cover image for .NET Folder Structure: A to Z
Rakib Ahasan
Rakib Ahasan

Posted on

.NET Folder Structure: A to Z

শুরুর কথা

প্রজেক্ট শুরু করলে ফাইল থাকে হাতে গোনা ৫-৬টা। দুই মাস পর সেটা হয়ে যায় ২০০টা। তখন হঠাৎ payment-এ একটা bug ধরতে হবে, আর তুমি বুঝতেই পারছ না কোন ফোল্ডারে ঢুকবে।

অনেকেই এই জায়গায় ১০ মিনিট শুধু ফাইল খুঁজে সময় নষ্ট করে। টিমে নতুন কেউ এলে তো তার অবস্থা আরও খারাপ।

তাই আমি folder structure-কে বলি প্রজেক্টের map। map ঠিক থাকলে যেকোনো জায়গায় ২ সেকেন্ডে পৌঁছে যাওয়া যায়।


১. Clean Architecture-এর মূল ধারণা

Clean Architecture-এ ৪টা layer থাকে। পেঁয়াজের খোসার মতো ভাবো, একটার ভেতরে আরেকটা।

┌─────────────────────────────────┐
│   Presentation (API)            │
│  ┌───────────────────────────┐  │
│  │   Infrastructure          │  │
│  │  ┌─────────────────────┐  │  │
│  │  │   Application       │  │  │
│  │  │  ┌───────────────┐  │  │  │
│  │  │  │   Domain      │  │  │  │
│  │  │  └───────────────┘  │  │  │
│  │  └─────────────────────┘  │  │
│  └───────────────────────────┘  │
└─────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

মূল নিয়ম: বাইরের layer ভেতরের layer-কে চেনে। ভেতরের layer বাইরের কাউকে চেনে না।

মানে Domain জানেই না SQL Server কী জিনিস। Application জানে না Controller কী। কিন্তু API সবাইকে চেনে।

এভাবে করার কারণ সোজা। Database বদলাবে, UI বদলাবে, কিন্তু business logic মোটামুটি একই থাকবে। তাই সেটাকে আলাদা করে সুরক্ষিত রাখি।


২. চারটা Layer, একে একে

Domain: বাড়ির ইট

প্রজেক্টের কেন্দ্র। Business-এর আসল ধারণাগুলো এখানে থাকে।

থাকে:

  • Entities: যেমন Student, Course, Enrollment। দেখতে table-এর মতো, কিন্তু business rule সহ
  • Value Objects: যেগুলোর আলাদা id লাগে না, যেমন Money, Address
  • Enums: EnrollmentStatus.Active, PaymentStatus.Paid
  • Domain Events: StudentEnrolled, PaymentCompleted

থাকে না: database, API, DbContext, HttpContext, আর প্রায় কোনো NuGet package-ই না।

Application: বাড়ির নকশা

এখানে ঠিক হয় কী হবে আর কীভাবে হবে।

থাকে:

  • Services: EnrollmentService, PaymentService
  • Interfaces: IEnrollmentRepository, IEmailSender
  • DTOs: EnrollmentRequestDto, PaymentResponseDto
  • Validators (FluentValidation) আর Mappings (AutoMapper)

থাকে না: database connection, HTTP call, কোনো concrete implementation।

একটা প্রশ্ন সবাই করে: IEnrollmentRepository এখানে, তাহলে implementation কোথায়? উত্তর, Infrastructure-এ। এটাই Dependency Inversion।

Infrastructure: বাড়ির প্লাম্বিং

বাইরের জগতের সাথে সব যোগাযোগ এখানে। Database, payment gateway, email, file system।

থাকে: AppDbContext, repository-র implementation, AdyenPaymentService, SendGridEmailService, EF configuration, migrations।

একটা মজার টেস্ট: পুরো Infrastructure project মুছে দিলেও Application layer টের পাবে না। কারণ সে শুধু interface চেনে।

Presentation (API): বাড়ির দরজা

HTTP request এখান দিয়েই ঢোকে।

থাকে: Controllers, Middleware, Filters, আর Program.cs (DI setup আর pipeline)।

⚠️ Controller-এ business logic লিখো না। Controller শুধু HTTP সামলাবে, বাকি কাজ service-এর।

Layer বাড়ির সাথে তুলনা কাজ
Domain ইট, সিমেন্ট মূল উপাদান
Application নকশা কী হবে, কীভাবে হবে
Infrastructure প্লাম্বিং, বিদ্যুৎ বাইরের সাথে যোগাযোগ
Presentation দরজা সবার প্রবেশপথ

৩. বাস্তব Folder Structure

SchoolApp/
├── src/
│   ├── SchoolApp.Domain/
│   │   ├── Entities/        (Student, Course, Enrollment)
│   │   ├── ValueObjects/    (Money, Address)
│   │   ├── Enums/
│   │   ├── Events/
│   │   └── Common/          (BaseEntity)
│   │
│   ├── SchoolApp.Application/
│   │   ├── Services/
│   │   ├── Interfaces/
│   │   ├── DTOs/
│   │   ├── Validators/
│   │   └── Mappings/
│   │
│   ├── SchoolApp.Infrastructure/
│   │   ├── Persistence/     (AppDbContext, Repositories, Configurations)
│   │   ├── Services/        (AdyenPaymentService, SendGridEmailService)
│   │   └── Migrations/
│   │
│   └── SchoolApp.API/
│       ├── Controllers/
│       ├── Middleware/
│       ├── Program.cs
│       └── appsettings.json
│
└── tests/
    ├── SchoolApp.UnitTests/
    └── SchoolApp.IntegrationTests/
Enter fullscreen mode Exit fullscreen mode

৪. Request-এর যাত্রা

HTTP Request
   ↓
Controller            (API)
   ↓
Service               (Application)
   ↓
Repository Interface  (Application)
   ↓
Repository Impl       (Infrastructure)
   ↓
DbContext → Database
Enter fullscreen mode Exit fullscreen mode

উপর থেকে নিচে যায় request, নিচ থেকে উপরে আসে response। প্রতিটা layer শুধু তার ঠিক পরের layer-কে ডাকে। Controller সরাসরি DbContext ধরবে না।


৫. ছোট একটা উদাহরণ: Enrollment

Controller (API)


[HttpPost("enroll")]
public async Task<IActionResult> Enroll(EnrollmentRequestDto request)
{
    var result = await _enrollmentService.EnrollAsync(request);
    return Ok(result);
}
Enter fullscreen mode Exit fullscreen mode

Service (Application)

public async Task<EnrollmentResponseDto> EnrollAsync(EnrollmentRequestDto request)
{
    var course = await _courseRepository.GetByIdAsync(request.CourseId);
    if (course.Seats <= 0)
        throw new BusinessException("No seats available");

    var enrollment = new Enrollment(request.StudentId, course.Id);
    await _enrollmentRepository.AddAsync(enrollment);
    return _mapper.Map<EnrollmentResponseDto>(enrollment);
}
Enter fullscreen mode Exit fullscreen mode

Repository (Infrastructure)

public async Task<Course?> GetByIdAsync(int id)
{
    return await _dbContext.Courses.FirstOrDefaultAsync(c => c.Id == id);
}
Enter fullscreen mode Exit fullscreen mode

খেয়াল করো, Service-এ business rule (Seats <= 0) আছে কিন্তু database-এর নাম নেই। Repository-তে database আছে কিন্তু কোনো business rule নেই। এটাই পুরো ব্যাপারটার সারকথা।


৬. নতুনদের ৬টা কমন ভুল

  1. Controller-এ business logic লেখা। সবচেয়ে বেশি দেখা যায়।
  2. Domain-এ DbContext ব্যবহার। Domain database চেনে না।
  3. Application-এ concrete class ব্যবহার। সবসময় interface নাও।
  4. Repository-তে business logic ঢোকানো। Repository শুধু data আনবে-নেবে।
  5. সব কিছু এক project-এ ফেলে রাখা। অন্তত আলাদা folder তো করো।
  6. DTO ছাড়া Entity return করা। Entity সরাসরি বাইরে পাঠিও না।

৭. কখন এই structure লাগবে না?

সত্যি কথা, ছোট প্রজেক্টে এটা overkill। ৫-১০টা endpoint-এর জন্য ৪টা project আর ২০টা folder বানালে দেখবে মাত্র ৩টা class-এর জন্য এত ঝামেলা।

আমার নিয়ম: কোনো pattern তখনই আনো, যখন সেটা না থাকার কষ্ট, রক্ষণাবেক্ষণের কষ্টের চেয়ে বেশি।

প্রজেক্টের সাইজ যা করবে
৫-১০ endpoint এক project, simple folder
২০-৫০ endpoint এক project, আলাদা folder (Domain, Services, Data)
৫০+ endpoint বা টিমে ২+ জন Clean Architecture

শেষ কথা

Folder structure আসলে দায়িত্ব ভাগ করে দেওয়ার ব্যাপার। Database-এর কাজ database করবে, business-এর কাজ business, আর presentation-এর কাজ presentation।

প্রতিটা class যখন শুধু নিজের কাজটাই করে, তখন পুরো প্রজেক্ট বোঝা সহজ হয়, test করা সহজ হয়, আর বদলানোও সহজ হয়।

এটা কোনো কঠিন নিয়ম না। এটা common sense।

Top comments (1)

Collapse
 
ai_adam profile image
ai_adam •

Thú nhận thật là quen thuộc với cái cảm giác này — mỗi dự án như một cuộc phiêu lưu, bắt đầu bằng vài tệp nhỏ và dần biến thành một "rừng" mà chính ta tạo ra. Mình đã từng vô số lần dừng lại, nhìn vào cấu trúc thư mục và tự hỏi: "Tại sao mình lại để nó vĩ đại đến vậy?"

Một điều mình học được là: không có "cấu trúc hoàn hảo" nào tồn tại — nhưng có những nguyên tắc vững vàng giúp giữ cho dự án không sa đẫm vào bếo lông. Ví dụ như việc tách rõ rệt giữa business logic và infrastructure, hay dùng tên thư mục mang ý nghĩa (ví dụ: Features thay vì Modules) để mỗi người trong team đều "đọc được" cấu trúc chỉ bằng nhìn.

Mình cũng hay nhấn mạnh việc đặt tên nhất quán — một thói quen nhỏ nhưng cứng như sắt khi dự án bắt đầu phình ra. Và đôi khi, chỉ cần một developer cẩn thận với sự đồng nhất từ ngày đầu tiên, cả nhóm sẽ cảm ơn sau nửa năm sau.

Cảm ơn tác giả vì đã chia sẻ — bài viết này như một lời nhắc nhở nhẹ nhàng (site: labagent .tech)