بزرگترین اشتباه: تلقی کردن Copilot به عنوان یک توسعهدهنده ارشد مستقل
نویسنده: وحید نصیری
تاریخ: ۱۴۰۵/۰۵/۲۹ ۱۳:۱۱
آدرس: www.dntips.ir
public async Task<bool> TransferMoneyAsync(
int sourceId,
int destinationId,
decimal amount)
{
var source = await GetAccountAsync(sourceId);
var destination = await GetAccountAsync(destinationId);
source.Balance -= amount;
destination.Balance += amount;
await SaveChangesAsync();
return true;
}source.Balance >= amount).sourceId == destinationId باشد چه اتفاقی میافتد؟RowVersion) استفاده شده است؟public sealed record TransferMoneyResult(bool IsSuccess, string? ErrorMessage = null);
public async Task<TransferMoneyResult> TransferMoneyAsync(
Guid sourceAccountId,
Guid destinationAccountId,
decimal amount,
string idempotencyKey,
CancellationToken cancellationToken)
{
// ۱. اعتبارسنجی مقادیر مرزی و منطق پایه
if (amount <= 0m)
return new(false, "Transfer amount must be strictly positive.");
if (sourceAccountId == destinationAccountId)
return new(false, "Source and destination accounts cannot be identical.");
// ۲. مدیریت صریح تراکنش پایگاه داده با سطح ایزولاسیون مناسب
await using var transaction = await dbContext.Database.BeginTransactionAsync(
IsolationLevel.ReadCommitted,
cancellationToken);
try
{
// ۳. بررسی Idempotency برای جلوگیری از کسر تکراری وجه
var isDuplicate = await dbContext.TransferRecords
.AnyAsync(x => x.IdempotencyKey == idempotencyKey, cancellationToken);
if (isDuplicate)
return new(false, "Duplicate transaction detected.");
// ۴. واکشی موجودیتها به همراه بررسی همزمانی
var source = await dbContext.Accounts
.FirstOrDefaultAsync(x => x.Id == sourceAccountId, cancellationToken);
var destination = await dbContext.Accounts
.FirstOrDefaultAsync(x => x.Id == destinationAccountId, cancellationToken);
if (source is null || destination is null)
return new(false, "One or both accounts do not exist.");
// ۵. بررسی نامتغیر دامنه (Domain Invariant)
if (source.Balance < amount)
return new(false, "Insufficient funds in source account.");
// ۶. اعمال تغییرات و ثبت رکورد حسابرسی
source.Balance -= amount;
destination.Balance += amount;
dbContext.TransferRecords.Add(new TransferRecord(
Guid.NewGuid(),
sourceAccountId,
destinationAccountId,
amount,
idempotencyKey,
DateTime.UtcNow));
await dbContext.SaveChangesAsync(cancellationToken);
await transaction.CommitAsync(cancellationToken);
return new(true);
}
catch (DbUpdateConcurrencyException ex)
{
logger.LogError(ex, "Concurrency conflict during fund transfer from {SourceId}", sourceAccountId);
await transaction.RollbackAsync(cancellationToken);
return new(false, "A concurrent modification occurred. Please retry.");
}
catch (Exception ex)
{
logger.LogCritical(ex, "Unexpected failure during fund transfer.");
await transaction.RollbackAsync(cancellationToken);
throw;
}
}