Panduan Pemrograman .NET
A). Development Environment
System Requirements
Untuk memastikan keseragaman lingkungan pengembangan, setiap developer diharuskan menggunakan spesifikasi minimal berikut:
- Operating System: Windows 10/11 atau macOS (Apple Silicon/Intel)
- .NET SDK: Versi 8.0.x (sesuai dengan LTS)
- Database Server: Microsoft SQL Server 2019 (Docker atau local installation)
- Cache: Redis (Docker atau local installation)
- Message Broker: NATS (Docker atau local installation)
- Package Manager:
dotnet tool
,NuGet
, dannpm
(untuk frontend jika diperlukan) - Source Control: DOT GitLab Self-hosted (https://gitlab.dot.co.id)
Tools & IDE
Berikut daftar tools yang bisa digunakan oleh setiap developer:
- IDE:
- Visual Studio Code (Disarankan)
- Visual Studio 2022 (Alternatif)
- JetBrains Rider (Alternatif)
- Database Management:
- DBeaver (Disarankan)
- Azure Data Studio (Alternatif)
- Redis Management:
- RedisInsight
- NATS Management:
- NATS Nui
- CLI:
nats-server
,nats
- Git Client:
- CLI atau GUI seperti Fork, GitKraken, atau SourceTree
- API Testing:
- Apidog
- Postman
Local Development Preparation
Instalasi Framework .NET 8.0
dotnet --version
Pastikan output menunjukkan versi Pastikan output menunjukkan versi
8.0.x
. Jika belum terinstal, download dari Microsoft .NET.Setup Database (MSSQL 2019) menggunakan Docker
docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=YourPassword123!' -p 1433:1433 --name sqlserver -d mcr.microsoft.com/mssql/server:2019-latest
Setup Redis menggunakan Docker
docker run --name redis -p 6379:6379 -d redis
Setup NATS menggunakan Docker
docker run --name nats -p 4222:4222 -d nats
Development Best Practices
- Gunakan Docker untuk menjalankan service dependency agar konsisten.
- Pastikan semua environment variable didefinisikan dalam
appsettings.Development.json
. - Pastikan semua service berjalan sebelum memulai development (
docker ps
).
B). Project Structure & Architecture
Project Structure & Anatomy
Struktur base proyek .NET dirancang untuk memisahkan concern setiap komponen agar mudah dikelola, dikembangkan, dan di-scale. Mengikuti prinsip Clean Architecture, proyek dibagi menjadi beberapa layer dengan tanggung jawab masing-masing.
├── Commands
├── Constants
│ ├── Cache
│ ├── CircuitBreaker
│ ├── Event
│ ├── Logger
│ ├── Permission
│ └── Storage
├── Domain
│ ├── Auth
│ │ ├── Dtos
│ │ ├── Messages
│ │ ├── Repositories
│ │ ├── Services
│ │ ├── Token
│ │ └── Util
│ ├── File
│ │ ├── Dto
│ │ └── Services
│ ├── Logging
│ │ ├── Listeners
│ │ └── Services
│ ├── Notification
│ │ ├── Dtos
│ │ ├── Messages
│ │ ├── Repositories
│ │ └── Services
│ ├── Permission
│ │ ├── Dtos
│ │ ├── Messages
│ │ ├── Repositories
│ │ └── Services
│ ├── Role
│ │ ├── Dtos
│ │ ├── Messages
│ │ ├── Repositories
│ │ └── Services
│ └── User
│ ├── Dtos
│ ├── Messages
│ ├── Repositories
│ └── Services
├── Http
│ └── API
│ ├── Health
│ └── Version1
│ ├── Auth
│ │ └── Controllers
│ ├── EmailExample
│ │ └── Controllers
│ ├── File
│ │ └── Controllers
│ ├── Notification
│ ├── Permission
│ │ └── Controllers
│ ├── Role
│ │ └── Controllers
│ └── User
│ └── Controllers
├── Immutables
├── Infrastructure
│ ├── Attributes
│ ├── BackgroundHosted
│ ├── Databases
│ ├── Dtos
│ ├── Email
│ ├── Events
│ ├── Exceptions
│ ├── Filters
│ ├── Helpers
│ ├── Integrations
│ │ ├── Http
│ │ └── NATs
│ ├── Jobs
│ ├── Logging
│ ├── Middlewares
│ ├── ModelBinders
│ ├── Queues
│ ├── Regexs
│ ├── Repositories
│ ├── Seeders
│ ├── Shareds
│ └── Subscriptions
├── Log
├── Migrations
├── Models
├── Properties
├── SeedersData
├── Storage
├── bin
│ └── Debug
│ └── net8.0
│ ├── SeedersData
│ ├── cs
│ ├── de
│ ├── es
│ ├── fr
│ ├── it
│ ├── ja
│ ├── ko
│ ├── pl
│ ├── pt-BR
│ ├── ru
│ ├── runtimes
│ │ ├── unix
│ │ │ └── lib
│ │ │ ├── net6.0
│ │ │ ├── netcoreapp2.1
│ │ │ ├── netcoreapp3.0
│ │ │ └── netcoreapp3.1
│ │ ├── win
│ │ │ └── lib
│ │ │ ├── net6.0
│ │ │ ├── net8.0
│ │ │ ├── netcoreapp2.1
│ │ │ ├── netcoreapp3.0
│ │ │ ├── netcoreapp3.1
│ │ │ └── netstandard2.0
│ │ ├── win-arm
│ │ │ └── native
│ │ ├── win-arm64
│ │ │ └── native
│ │ ├── win-x64
│ │ │ └── native
│ │ └── win-x86
│ │ └── native
│ ├── tr
│ ├── zh-Hans
│ └── zh-Hant
├── copilot-prompts
├── dbdocs
├── deploy
│ └── ansible
└── obj
└── Debug
└── net8.0
├── ref
├── refint
└── staticwebassets
Berikut adalah deskripsi dari masing-masing folder utama:
Commands
- Berisi command handler untuk cli command.
- Contoh:
CreateUserCommand.cs
,UpdateUserCommand.cs
.
Constants
- Menyimpan nilai-nilai konstan yang digunakan dalam aplikasi.
- Subfolder:
- Cache → Key untuk Redis.
- CircuitBreaker → Konfigurasi untuk circuit breaker.
- Event → Nama event untuk messaging.
- Logger → Nama kategori log.
- Permission → Role-based permission.
- Storage → Path atau konfigurasi penyimpanan.
Domain
- Layer domain berisi core business logic tanpa bergantung pada infrastruktur eksternal.
- Dibagi berdasarkan bounded context seperti:
- Auth, File, Logging, Notification, Permission, Role, User.
- Setiap submodul memiliki:
- Dtos → Data Transfer Objects.
- Messages → Event atau command message.
- Repositories → Interface untuk database access.
- Services → Business logic.
- Token / Util → Utility function.
Http
- Berisi controller API dan dikelompokkan berdasarkan versioning.
- Struktur:
pgsql
CopyEdit
/Http
└── API
├── Health
└── Version1
├── Auth
│ └── Controllers
├── EmailExample
│ └── Controllers
├── File
│ └── Controllers
├── Notification
├── Permission
│ └── Controllers
├── Role
│ └── Controllers
└── User
└── Controllers
- Setiap API memiliki controller dalam folder yang sesuai dengan domainnya.
Immutables
- Berisi data yang bersifat immutable (tidak berubah), seperti konfigurasi statis atau daftar konstan yang digunakan di beberapa bagian aplikasi.
Infrastructure
- Implementasi teknis yang mendukung aplikasi.
- Subfolder:
- Attributes → Custom attributes untuk annotation.
- BackgroundHosted → Background service menggunakan
IHostedService
. - Databases → Konfigurasi database dan migration.
- Dtos → DTO umum yang digunakan di seluruh infrastruktur.
- Email → Implementasi email sender.
- Events → Event handler.
- Exceptions → Custom exception.
- Filters → Action filter untuk middleware.
- Helpers → Utility/helper class.
- Integrations:
- Http → API client.
- NATS → Message broker client.
- Jobs → Scheduled background job.
- Logging → Logging provider.
- Middlewares → Middleware untuk request pipeline.
- ModelBinders → Custom model binding.
- Queues → Queue system untuk antrian proses.
- Regexs → Regular expressions.
- Repositories → Implementasi repository dari domain.
- Seeders → Data seeder untuk inisialisasi database.
- Shareds → Shared utility class.
- Subscriptions → Event subscription handler.
Log
- Menyimpan log aplikasi, bisa berupa file atau konfigurasi logging.
Migrations
- Menyimpan migration database Entity Framework Core.
Models
- Model untuk Entity Framework Core.
Properties
- Berisi konfigurasi proyek seperti
launchSettings.json
untuk debugging.
SeedersData
- Dataset untuk seeding database.
Storage
- Penyimpanan file (jika menggunakan sistem file lokal).
bin / obj
- Hasil build dari proyek, tidak perlu di-tracking di Git.
copilot-prompts
- Prompt atau script untuk AI Copilot.
dbdocs
- Dokumentasi database (misal, menggunakan dbdocs.io).
deploy
- Folder untuk konfigurasi deployment, misalnya:
- Ansible → Deployment automation script.
Naming Convention
- File Name
untuk file name menggunakan PascalCase
contoh:
UserController.cs
UserService.cs
UserModel.cs
UserInterface.cs
- Classes / Interface / Enum
untuk class / interface / enum / Constant menggunakan PascalCase
contoh:
public class UserController {}
public enum UserRoleEnum {}
public interface IUserService {}
- Method and Variable
untuk function public gunakan PascalCase
sedangkan untuk selain public bisa menggunakan camelCase
dan diusahakan untuk descriptive contoh:
public async Task<UserResponseDto> GetUserById(Guid id, bool isActive = true, bool throwExceptionIfNotFound = true) {}
untuk variable name gunakan camelCase
dan diusahakan untuk descriptive contoh:
var userActives = users.Select(x => x.isActive).ToList();
C). Logging & Monitoring
Logging & Monitoring sangat penting untuk memastikan aplikasi dapat dipantau dengan baik dan menangani error secara efektif. Berikut adalah struktur yang bisa digunakan:
Best Practice
- Gunakan structured logging agar log lebih mudah dianalisis.
- Hindari logging data sensitif seperti password, token, atau informasi pribadi.
- Gunakan log level dengan benar:
Trace
→ Debugging mendalamDebug
→ Informasi debuggingInformation
→ Event utama aplikasi (misalnya, user login)Warning
→ Masalah potensial (misalnya, timeout request)Error
→ Kesalahan yang perlu diperbaikiCritical
→ Error kritis yang bisa menyebabkan downtime
Contoh
_logger.LogInformation("User {UserId} logged in at {Time}", userId, DateTime.UtcNow);
_logger.LogError(ex, "Failed to process request for {RequestId}", requestId);
Error Monitoring
- Gunakan middleware untuk menangkap semua unhandled exceptions secara global.
- Pastikan error dicatat di sistem logging dan bisa dikirim ke monitoring tools seperti Sentry, Application Insights, atau ELK Stack.
Security Concern
- Jangan pernah logging password, API keys, atau data sensitif lainnya.
- Jika menggunakan logging eksternal (seperti Sentry atau ELK), pastikan data yang dikirim sudah dianonimkan.
- Gunakan log retention policy agar log tidak terus tumbuh tanpa batas dan membebani storage.
Hindari:
_logger.LogInformation("User logged in with password: {Password}", user.Password);
Sebagai gantinya, gunakan ID atau informasi anonim:
_logger.LogInformation("User {UserId} logged in successfully", user.Id);
D). Error Handling
Best Practice
Error handling yang baik membantu mengurangi bug yang sulit dilacak dan memastikan aplikasi tetap berjalan dengan baik.
Menggunakan try-catch
dengan Bijak
- Gunakan
try-catch
hanya pada operasi yang rentan gagal, seperti database query, HTTP request, atau file I/O. - Hindari
try-catch
berlebihan yang bisa menyembunyikan error atau membuat debugging lebih sulit. - Jangan tangkap
Exception
secara umum tanpa menangani error dengan benar.
BAD PRACTICE (Menangkap semua exception tanpa logging)
try
{
var data = await _service.GetDataAsync();
}
catch (Exception)
{
// Tidak melakukan apa-apa, sulit untuk debugging
}
GOOD PRACTICE (Tangani exception dengan logging dan rethrow jika perlu)
try
{
var data = await _service.GetDataAsync();
}
catch (HttpRequestException ex)
{
_logger.LogError(ex, "Error fetching data from external API.");
throw new CustomApiException("Failed to fetch data", ex);
}
Membedakan Exception yang Dilempar (****throw
****)
- Gunakan exception bawaan yang sesuai seperti
ArgumentException
,InvalidOperationException
, dll. - Gunakan custom exception jika ada kasus spesifik dalam domain aplikasi.
- Jangan tangkap dan rethrow
Exception
secara langsung tanpa menambahkan konteks.
Contoh Custom Exception
public class NotFoundException : Exception
{
public NotFoundException(string message) : base(message) { }
}
Penggunaan di Service Layer
public async Task<User> GetUserByIdAsync(int id)
{
var user = await _dbContext.Users.FindAsync(id);
if (user == null)
{
throw new NotFoundException($"User with ID {id} not found.");
}
return user;
}
Global Exception Handling dengan Middleware
- Gunakan middleware untuk menangani error secara global, sehingga tidak perlu
try-catch
di setiap controller. - Pastikan response API tetap terstruktur dan user-friendly.
Contoh global exception middleware
public class ExceptionHandlingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ExceptionHandlingMiddleware> _logger;
public ExceptionHandlingMiddleware(RequestDelegate next, ILogger<ExceptionHandlingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
_logger.LogError(ex, "Unhandled exception occurred.");
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
await context.Response.WriteAsJsonAsync(new { Message = "An unexpected error occurred" });
}
}
}
Pada global exception middleware, response body dan status code juga dapat disesuaikan berdasarkan tipe exception yang terjadi, misalnya seperti berikut:
try
{
await _next(context);
}
catch(Exeption ex)
{
HttpStatusCode statusCode;
string errorMessage;
switch(error)
{
case DataNotFoundException:
statusCode = HttpStatusCode.NotFound;
errorMessage = "Data not found";
break;
case UnauthorizedAccessException:
statusCode = HttpStatusCode.Unauthorized;
errorMessage = "You are not allowed to access this resource";
break;
default:
statusCode = HttpStatusCode.InternalServerError;
errorMessage = ex.Message;
_logger.LogError(ex, "Unhandled exception occurred.");
break;
}
context.Response.StatusCode = statusCode;
await context.Response.WriteAsJsonAsync(new { Message = errorMessage });
}
Rollback (Database & External Service Cleanup)
Database Transaction Rollback
Jika terjadi error saat melakukan operasi database, pastikan transaksi dibatalkan untuk menghindari data yang tidak konsisten.
Contoh menggunakan DbContextTransaction
di Entity Framework Core:
public async Task PlaceOrderAsync(Order order)
{
using var transaction = await _dbContext.Database.BeginTransactionAsync();
try
{
_dbContext.Orders.Add(order);
await _dbContext.SaveChangesAsync();
await _paymentService.ProcessPaymentAsync(order);
await transaction.CommitAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error occurred while placing an order. Rolling back transaction.");
await transaction.RollbackAsync();
throw;
}
}
Rollback Data di External Service (Misalnya, Microservice atau API Call)
Jika aplikasi berinteraksi dengan layanan eksternal, perlu strategi untuk rollback atau compensating action jika terjadi kegagalan.
Pendekatan yang bisa digunakan:
- Soft Delete atau Status Update
- Jika data sudah dibuat di layanan eksternal, tandai sebagai
CANCELLED
atauFAILED
daripada menghapusnya.
- Jika data sudah dibuat di layanan eksternal, tandai sebagai
- Compensating Transaction (Saga Pattern)
- Untuk microservices, bisa gunakan event-driven architecture untuk membatalkan transaksi yang sudah berjalan.
Contoh: Rollback Order jika pembayaran gagal
public async Task PlaceOrderWithExternalPaymentAsync(Order order)
{
using var transaction = await _dbContext.Database.BeginTransactionAsync();
try
{
_dbContext.Orders.Add(order);
await _dbContext.SaveChangesAsync();
var paymentSuccess = await _paymentService.ProcessPaymentAsync(order);
if (!paymentSuccess)
{
throw new PaymentFailedException("Payment failed.");
}
await transaction.CommitAsync();
}
catch (PaymentFailedException ex)
{
_logger.LogWarning("Payment failed, rolling back order.");
await transaction.RollbackAsync();
await _externalOrderService.CancelOrderAsync(order.Id); // Rollback external order
throw;
}
catch (Exception ex)
{
_logger.LogError(ex, "Unexpected error while placing order.");
await transaction.RollbackAsync();
throw;
}
}
E). Performance & Scalability
- Gunakan Asynchronous Programming (****
async/await
****) untuk mencegah blocking yang bisa memperlambat aplikasi. - Hindari Query Berulang ke Database dalam loop, gunakan
Include()
atauBatch Query
. - Gunakan Caching untuk mengurangi beban database atau API eksternal.
- Optimalkan I/O Bound Work seperti file handling, database query, dan network request.
- Gunakan Connection Pooling untuk meningkatkan efisiensi akses database dan service eksternal.
- Gunakan Streaming daripada Memuat Semua Data ke Memory jika menangani data besar.
3rd Party Network Request
Ketika berinteraksi dengan API eksternal, optimasi perlu dilakukan untuk mengurangi latensi dan meningkatkan efisiensi.
Gunakan HttpClient
dengan HttpClientFactory
- Jangan buat
new HttpClient()
secara langsung di setiap request, karena akan menyebabkan masalah koneksi (socket exhaustion). - Gunakan
IHttpClientFactory
untuk mengelolaHttpClient
dengan lebih baik.
BAD PRACTICE (Membuat HttpClient
secara langsung)
public async Task<string> GetExternalDataAsync()
{
using var client = new HttpClient();
return await client.GetStringAsync("https://api.example.com/data");
}
GOOD PRACTICE (Menggunakan HttpClientFactory
****)
public class ExternalService
{
private readonly HttpClient _httpClient;
public ExternalService(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<string> GetExternalDataAsync()
{
return await _httpClient.GetStringAsync("https://api.example.com/data");
}
}
Batasi dan Optimalkan Request
- Gunakan Retry & Circuit Breaker dengan Polly untuk menangani request yang gagal.
- Gunakan Rate Limiting agar tidak membebani API eksternal.
F). Database & Query Optimization
Database sering menjadi bottleneck dalam aplikasi, sehingga perlu dioptimalkan.
Database Efficiency
Gunakan Indexing & Optimasi Query
- Pastikan tabel memiliki indeks pada kolom yang sering digunakan dalam
WHERE
,JOIN
, danORDER BY
. - Gunakan
AsNoTracking()
jika hanya membaca data tanpa perlu tracking perubahan.
BAD PRACTICE (Query lambat dan tidak optimal)
var users = await _dbContext.Users.Where(u => u.IsActive).ToListAsync();
GOOD PRACTICE (Menggunakan AsNoTracking()
dan Indexing)
var users = await _dbContext.Users.AsNoTracking().Where(u => u.IsActive).ToListAsync();
Gunakan Bulk Insert dan Batch Update
Jangan lakukan insert atau update dalam loop satu per satu, gunakan EF Core Bulk Operations atau Batch Update.
BAD PRACTICE (Insert satu per satu, lambat untuk data besar)
foreach (var user in users)
{
_dbContext.Users.Add(user);
}
await _dbContext.SaveChangesAsync();
GOOD PRACTICE (Gunakan Bulk Insert atau Batch Update)
await _dbContext.Users.AddRangeAsync(users);
await _dbContext.SaveChangesAsync();
Connection Pooling untuk Database
Gunakan connection pooling agar koneksi ke database lebih efisien dan tidak boros resource.
G). Dependency Management
Dependency Management sangat penting untuk memastikan aplikasi tetap ringan, aman, dan mudah dikelola. Beberapa prinsip utama yang harus diikuti:
- Gunakan Dependency Injection (DI) untuk menghindari tight coupling.
- Pilih library eksternal yang aktif dikembangkan dan memiliki dukungan komunitas.
- Gunakan versi library yang stabil, hindari menggunakan versi preview kecuali benar-benar diperlukan.
- Periksa lisensi library sebelum digunakan di proyek komersial.
- Gunakan paket NuGet resmi dan hindari dependensi yang tidak diperlukan.
3rd Party Library
.NET memiliki ekosistem NuGet yang luas. Berikut best practice untuk mengelola dependensi eksternal:
Gunakan PackageReference
untuk Dependency Management
.csproj
harus menggunakanPackageReference
, bukanpackages.config
.- Pastikan dependensi yang tidak digunakan dihapus dari proyek.
Contoh dalam .csproj
****:
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Serilog" Version="3.0.1" />
</ItemGroup>
Periksa semua dependensi yang digunakan dengan perintah:
dotnet list package
Hindari Menggunakan Versi yang Terlalu Spesifik
- Hindari menggunakan versi fixed (
Version="1.0.0"
), gunakan>=
jika memungkinkan. - Misalnya, gunakan
Version="1.0.*"
agar kompatibel dengan minor updates.
Perbarui Dependensi secara Berkala
Pastikan library selalu diperbarui untuk mendapatkan fitur terbaru dan patch keamanan.
- Periksa update dependensi:
dotnet list package --outdated
- Perbarui semua package ke versi terbaru yang kompatibel:
dotnet outdated --upgrade
H). Configuration Management
Configuration Management adalah aspek penting dalam pengembangan aplikasi .NET yang memastikan pengaturan sistem dapat dikelola dengan fleksibel, aman, dan mudah disesuaikan dengan berbagai environment (Development, Staging, Production). Dengan manajemen konfigurasi yang baik, aplikasi dapat dikonfigurasi tanpa perlu mengubah kode, mempermudah deployment, serta meningkatkan keamanan dengan menjaga informasi sensitif tetap terlindungi.
Struktur appsettings.json
Contoh struktur appsettings.json yang baik:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"System": "Error"
}
},
"ConnectionStrings": {
"DefaultConnection": "Server=myserver;Database=mydb;User Id=myuser;Password=mypassword;"
},
"Redis": {
"Host": "localhost",
"Port": 6379
},
"ExternalService": {
"BaseUrl": "https://api.example.com",
"ApiKey": "your-api-key"
}
}
Menggunakan Environment-Specific Configuration
.NET secara otomatis membaca file konfigurasi berdasarkan environment.
- Tambahkan
appsettings.{ENVIRONMENT}.json
appsettings.Development.json
appsettings.Production.json
- Pastikan file tambahan dimuat di
Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Configuration
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Membaca Konfigurasi Menggunakan IConfiguration
Untuk membaca konfigurasi langsung dari appsettings.json
:
public class MyService
{
private readonly IConfiguration _configuration;
public MyService(IConfiguration configuration)
{
_configuration = configuration;
}
public string GetApiUrl()
{
return _configuration["ExternalService:BaseUrl"];
// atau
return _configuration.GetValue<string>("ExternalService:BaseUrl");
}
}