Developer Advocate
Открыть в Telegram
1 035
Подписчики
Нет данных24 часа
Нет данных7 дней
-130 дней
Архив постов
1 035
🔹 مدلسازی داده مدرن در #CSharp با Records و Primary Constructors
یکی از پیشرفتهای مهم C# در سالهای اخیر، معرفی record types و primary constructors است که با هم، ساختارهای دادهای غیرقابل تغییر (immutable)، خوانا و نگهداشتپذیر را سادهتر از همیشه میسازند.
▸ Record چیست و چه زمانی استفاده کنیم؟
Recordها به طور پیشفرض مبتنی بر equality value هستند و برای مدلسازی data-centric objects فوقالعادهاند.
استفاده از records زمانی توصیه میشود که:
- موجودیت بر اساس مقدار (نه هویت) مقایسه میشود.
- به immutability نیاز دارید و میخواهید با
with کپیسازی انجام دهید.
- لایه Domain مدل را با الگوی DDD مدلسازی میکنید (مثل Value Object).
▸ Primary Constructor: خوانایی بالا و سینتکس تمیز
از نسخه ۱۲ به بعد، #CSharp امکان تعریف field/prop مستقیم در هدر type را میدهد:
public record Person(string FirstName, string LastName, int Age);
یا با رفتار سفارشی:
public record Person(string FirstName, string LastName, int Age)
{
public string FullName => $"{FirstName} {LastName}";
}
- میتوانید متدها، propertyهای محاسباتی، و حتی validate کردن پارامترها را اضافه کنید.
- از destructuring و pattern matching بهشکلی عالی پشتیبانی میشود.
▸ ترفند: سازگاری کامل با with و تغییرات غیرمخرب
کپیبرداری و اصلاح ویژگیها بدون تغییر اصل داده:
var original = new Person("Sahar", "Nouri", 30);
var updated = original with { Age = 31 };
▸ Best Practice:
- برای مدلسازی حجم زیادی از دادههای ساده (مانند DTOs و Value Objects)، همیشه record با primary constructor را انتخاب کنید.
- زمانی که به مقایسه بر اساس ریفرنس یا mutable state نیاز دارید، از class استفاده کنید.
- اگر نیاز به behavior پیچیده یا invariantهای خاص دارید، میتوانید خطا پرتاب یا مقداردهی پیشفرض انجام دهید:
public record Product(string Name, decimal Price)
{
public Product : this
{
if (string.IsNullOrWhiteSpace(Name))
throw new ArgumentException("Name is required");
}
}
مطالعهی بیشتر:
MS Docs: Record types and primary constructors in C# 12
Advanced C# Language Features
@DeveloperAdvocate 🥑1 035
مدیریت وضعیت در اپلیکیشنهای React همواره چالشبرانگیز بوده است، بهخصوص زمانی که نیاز به خوانایی، مقیاسپذیری و سادگی همزمان داریم. کتابخانه Zustand پاسخی است به این نیاز که با کمترین بالاسری و minimum API، state management را به سادهترین و موثرترین شکل ارائه میکند—مشابه خیرگی lightweight نصب و وانگیری dependency injection در معماریهای مدرن .NET.
مزیت اصلی Zustand این است: state global را خارج از context API و با performance فوقالعاده (بدون renderهای اضافی)، به صورت یک store ساده JS/TS نگهداری میکنید. هر فایل store یک منبع معتبر (single source of truth) است که خارج از ساختارهای درختی React تعریف میشود وهمچون singleton در پروژههای .NET Core، همیشه در دسترس است.
نمونه پیادهسازی سادهی store با Zustand برای counter:
import { create } from 'zustand'
const useCounterStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 }))
}))
در کامپوننت:
function Counter() {
const { count, increment, decrement } = useCounterStore()
return (
<div>
<button onClick={decrement}>-</button>
<span>{count}</span>
<button onClick={increment}>+</button>
</div>
)
}
ویژگی برجسته Zustand امکان تفکیک selectorها و عدم trigger شدن render غیرضروری است؛ به این صورت:
const count = useCounterStore(state => state.count)
مثلاً تنها count تغییر کند، فقط همان کامپوننت render میشود.
حالا اگر stateهای پیچیدهتر دارید (domain-driven state objects)، Zustand بهراحتی قابلیت ترکیب چند store مختلف و اشتراک بین contextها را فراهم میکند؛ دقیقاً مشابه patternهای Composite Root در معماریهای Clean/.NET.
نکته کلیدی برای استفاده مؤثر:
- Define هر store را در یک فایل مستقل، تحت نامگذاری domain-centric نگه دارید، مشابه رویکردهای feature-based یا vertical slice architecture در backend.
- استفاده از middlewareها مانند persist یا devtools، امکان debug و حفظ state بین reload را فراهم میکند (summon مانند ابزارهای سری EntityFramework و AutoMapper در dotnet).
یک منبع عمیقتر:
https://medium.com/@ismaelneto/zustand-the-bearable-state-manager-for-react-19e1e89fa7f1
#React #StateManagement #Zustand #AdvancedPatterns
Advanced React Concepts
@DeveloperAdvocate 🥑1 035
🎯 تمایز احراز هویت (Authentication) و مجوزدهی (Authorization)؛ اصول، تفاوتها و پیادهسازی عملی
🔑 مفهوم شناسی سریع:
- احراز هویت یعنی بررسی اینکه "کاربر کیست؟"
- مجوزدهی یعنی تعیین اینکه "این کاربر به چه چیزهایی اجازه دسترسی دارد؟"
مثال ملموس: شما با کارت ملی وارد یک ساختمان میشوید (احراز هویت)؛ اما تنها به بخشهایی که مجوز دارید دسترسی پیدا میکنید (مجوزدهی).
---
🤝 پیادهسازی احراز هویت امن:
در پروژههای .NET، معمولا گزینههایی مانند OpenID Connect (OIDC) روی OAuth 2.0 استفاده میشود.
برای مثال، استفاده از افزونه
Microsoft.AspNetCore.Authentication.OpenIdConnect:
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
options.Authority = "https://login.example.com";
options.ClientId = "my-app-client";
options.ClientSecret = "********";
options.ResponseType = "code";
options.SaveTokens = true;
});
با این تنظیمات، کاربر صرفا پس از تایید هویت توسط Identity Provider اجازه ورود به سیستم را پیدا میکند.
---
🔒 پیادهسازی مجوزدهی: RBAC و ABAC
- RBAC (Role-Based Access Control) سادهترین نوع مجوزدهی است؛ هر کاربر به نقشی (Role) منسوب میشود و دسترسیها براساس نقش تعیین میشود:
[Authorize(Roles = "Admin,Manager")]
public IActionResult SecureArea() => View();
- ABAC (Attribute-Based Access Control) سطح بالاتر کنترل است که دسترسی را به کمک ویژگیهای کاربر یا منابع اجرایی میکند؛ مثلاً با Policy:
services.AddAuthorization(options =>
{
options.AddPolicy("DepartmentPolicy", policy =>
policy.RequireClaim("Department", "IT"));
});
و در کنترلر:
[Authorize(Policy = "DepartmentPolicy")]
public IActionResult ITOnlyResource() => View();
---
📚 مطالعهی عمیقتر:
Authentication and Authorization in ASP.NET Core
✨ همیشه این اصل را به یاد داشته باشید: احراز هویت و مجوزدهی نباید با هم ادغام شوند؛ جداسازی آنها هم امنیت را بالا میبرد، هم انعطاف معماری را تضمین میکند.
Quality Attributes & Cross-Cutting Concerns
@DeveloperAdvocate 🥑1 035
اگر تا به حال با سؤال «چرا IEnumerable<out T> کوورینت است ولی IList<T> نه؟» مواجه شدهاید، وقت آن رسیده که با «کوواریانس» (Covariance)، «کانتراواریانس» (Contravariance) و تحولات اخیر در «جنریک مث» .NET عمیقتر آشنا شوید.
✔️ کوواریانس در جنریکها مستلزم اضافهکردن کلیدواژه out در تعریف اینترفیس یا دلیگیت است؛ یعنی اگر نوع A زیرنوع B باشد، آنگاه IEnumerable<A> را به عنوان IEnumerable<B> میتوانید استفاده کنید، زیرا فقط پارامترها را خروجی میگیرید (فقط get). اما لیستی مانند
IList<T> هم پارامتر را میگیرد (set) و هم خارج میکند (get) پس نوع T نه out و نه in صرف است و امکان variance وجود ندارد. به مثال زیر دقت کنید:
IEnumerable<string> strs = new List<string>();
IEnumerable<object> objs = strs; // معتبر، به خاطر covariance
// IList<string> به IList<object> قابل تبدیل نیست
✔️ کانتراواریانس برعکس است: اینترفیسهایی که پارامتر تایپی را فقط مصرف میکنند (مثلاً دلیگیتهای اینجکشن) با کلیدواژه in موجوز contravariant میشوند:
Action<object> actObj = o => Console.WriteLine(o);
Action<string> actStr = actObj; // معتبر، به خاطر contravariance
🔥 جنریک مث در C# 11 و .NET 7+
یکی از نقطههای تحولبرانگیز در سالهای اخیر، اضافهشدن اینترفیسهای جدید مثل INumber<TSelf> و IMath<TSelf> در System.Numerics است که اجازه میدهد الگوریتمهای ریاضیاتی را به شکل جنریک بنویسید؛ بینیاز به تکرار کد برای int، double، یا سایر انواع عددی.
مثال سادهی جمعزدن جنریک بدون تکرار:
using System.Numerics;
T Sum<T>(IEnumerable<T> items) where T : INumber<T>
{
T sum = T.Zero;
foreach (var item in items)
sum += item;
return sum;
}
✅ حالا میتوانید با انواعی مثل int، double، یا حتی BigInteger با همین کد کار کنید!
مطالعهی بیشتر و کدهای عمیقتر را از این مقاله بخوانید:
Covariance, Contravariance, and Generic Math in C# - Microsoft Docs
Advanced C# Language Features
@DeveloperAdvocate 🥑1 035
🎯 Custom Hooks: الگویی برای ساخت منطق قابل استفاده مجدد و بهینه
یکی از الگوهای پیشرفته در توسعه SPA با React، ساخت "Custom Hooks" است. اما در پروژههای enterprise، ایجاد هوکهایی برای حل مسائل پرتکرار مثل مدیریت فرم یا واکشی داده و اطمینان از بهینگی و قابلیت تست، یک چالش جدیست. بیایید یک نمونهٔ پیشرفته از هوک مدیریت فرم با یکپارچهسازی اعتبارسنجی و بهینهسازیهای performance را بررسی کنیم:
import { useReducer, useCallback } from 'react';
function formReducer(state, action) {
switch(action.type) {
case 'CHANGE':
return {
...state,
values: { ...state.values, [action.field]: action.value },
errors: { ...state.errors, [action.field]: action.error }
};
case 'RESET':
return action.initialState;
default: return state;
}
}
export function useForm(initialValues, validators) {
const initialState = {
values: initialValues,
errors: Object.keys(initialValues).reduce((a, c) => ({ ...a, [c]: null }), {})
};
const [state, dispatch] = useReducer(formReducer, initialState);
const validate = useCallback(
(field, value) => validators[field]?.(value) ?? null,
[validators]
);
const handleChange = useCallback(
e => {
const { name, value } = e.target;
dispatch({
type: 'CHANGE',
field: name,
value,
error: validate(name, value)
});
},
[validate]
);
const reset = useCallback(
() => dispatch({ type: 'RESET', initialState }),
[initialState]
);
// بهینگی تغییر reference توابع برای جلوگیری از render اضافی
return {
values: state.values,
errors: state.errors,
handleChange,
reset,
isValid: Object.values(state.errors).every(x => !x)
};
}
کاربرد:
const validators = {
email: v => (/^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$/i.test(v) ? null : 'Invalid email'),
password: v => (v.length >= 8 ? null : 'Min 8 chars')
};
const { values, errors, handleChange, isValid, reset } = useForm(
{ email: '', password: '' },
validators
);
📚 منبع بیشتر: مقاله «Building reusable custom hooks in React» از dev.to
https://dev.to/vvo/building-reusable-custom-hooks-in-react-1lfa
Advanced React Concepts
@DeveloperAdvocate 🥑1 035
یک مهارت حیاتی برای رهبران فنی: واگذاری مؤثر (Delegation). بسیاری از معماران نرمافزار و رهبران تیم، برای حفظ کیفیت فنی یا اطمینان از پیشرفت، بیش از حد دست در کارها میبرند و به آسیبهای زیر دچار میشوند: کاهش انگیزه تیم، خستگی ذهنی خودشان، و گلوگاه شدن پروژه.
چالش اصلی: «چهطور مطمئن شویم کارها درست انجام میشوند، در حالی که خودمان درگیرِ هر جزئیات نمیشویم؟»
یک الگوی ساده و فنی با استفاده از delegation در توسعه نرمافزار:
۱. تقسیم کار به وظایف مستقل، با خروجی قابل سنجش (Outcome Driven)
۲. اعتماد به تیم، با مشخص کردن Definition of Done و خروجیهای دقیق
۳. بازخور هدفمند (Targeted Feedback) بجای micro-management
۴. واگذاری مالکیت (Code/Task Ownership) همراه با Auto-Validation (Code Review, CI Pipelines)
نمونه ساده تقسیم و واگذاری مسئولیت در یک پروژه ASP.NET Core با Command Handlerها:
// Command برای ایجاد یک کار جدید
public record CreateTaskCommand(string Title, string AssignedTo);
// Handler مسئولیت اجرای Business Logic را دارد
public class CreateTaskCommandHandler : IRequestHandler<CreateTaskCommand, Guid>
{
private readonly ITaskRepository _repository;
private readonly INotificationService _notify;
public CreateTaskCommandHandler(ITaskRepository repository, INotificationService notify)
{
_repository = repository;
_notify = notify;
}
public async Task<Guid> Handle(CreateTaskCommand command, CancellationToken ct)
{
var task = new TaskEntity { Title = command.Title, Owner = command.AssignedTo };
await _repository.SaveAsync(task, ct);
await _notify.SendAsync(task.Owner, $"New Task Assigned: {task.Title}");
return task.Id;
}
}
اینجا مسئولیتها به شکل واضح بین Handler، Repository و Notification Service تقسیم شده و تستپذیری، انتقال مسئولیت و واگذاری ownership تقویت شده است.
در نهایت، هر مسألهای که قرار است واگذار شود باید سه اصل شفافیت انتظارات، مسئولیتپذیری و بازخورد سریع داشته باشد. تفویض هوشمند، تیم شما را توسعه میدهد ـ هم فنی، هم مدیریتی.
مطالعه بیشتر (انگلیسی):
Delegation: Martin Fowler
Technical Leadership & Mentoring
@DeveloperAdvocate 🥑1 035
در اکوسیستم React اغلب پرسشی تکراری بین توسعهدهندگان ارشد مطرح میشود: آیا «Context API» برای مدیریت state جهانی کفایت میکند یا بهتر است سراغ کتابخانههای تخصصی مانند Redux Toolkit، Zustand یا Jotai برویم؟ بیایید به لایههای زیرساختی این تصمیمگیری نگاهی عمیقتر داشته باشیم.
🔹 Context API:
Context ذاتاً برای اشتراکگذاری state یا dependency injection در سطوح مختلف component tree طراحی شده، نه برای مدیریت داده با تغییرات مکرر و شدید. key drawbacks:
- همیشه هنگام تغییر context، تمام consumerها re-render میشوند (مگر با pattern پیشرفته selector).
- debugging و کنترل mutation پیچیدهتر میشود.
- امکان مشاهده و مدیریت flow تغییر state (مانند time travel/debugging) بسیار محدود است.
🔹 Redux Toolkit:
Redux (مخصوصاً با toolkit) استاندارد صنعتی برای مدیریت state پیچیده با flow روشن (actions, reducers, middleware) و ابزارهای DevTools قدرتمند را فراهم میآورد.
- مناسب برای اپلیکیشنهای بزرگ با حالتهای (state) چندبخشی و نیاز به audit/debugging.
- اما مقدار boilerplate هنوز نسبی باقی میماند (گرچه با sliceها بهبود یافته).
🔹 Zustand / Jotai:
این کتابخانهها به سمت طراحی minimal رفتهاند و state را بیرون از React tree مدیریت میکنند (storeهای مجزا).
- رویکرد reactive و API ساده؛ بدون نیاز به provider wrapping.
- مناسب برای پروژههایی با state غیر زیاد اما توزیع نسبی (یعنی هنجارشکن بدون prop drilling).
- ابزارهای debugging محدودتر نسبت به Redux.
🎯 کی از Context استفاده کنیم؟
- وقتی ساختار state ساده است؛ مثل theme، user locale، یا authorization token.
- برای dependency injection یا static config که کم تغییر میکنند.
- با آگاهی از اینکه useContext برای stateهای frequently changing کارایی ایدهآلی ندارد.
🎯 زمان مهاجرت به state management library:
- وقتی state وابستگیهای متقابل پیدا میکند، نیاز به undo/redo یا persisting پیدا میشود، یا به performance observable نیازمندید.
- ترکیب optimistic updates، ارثبری multi-source state یا integration با middlewareهایی مانند logging و side effects.
یک snippet جهت تزریق state سنگین در Context (و trap رایج re-render):
// معادل JavaScript برای فهم موضوع توسط devهای .NET
const MyContext = React.createContext();
function Provider({ children }) {
const [state, setState] = useState({ /*...*/ });
// تغییر کوچک، تمام consumerها را re-render میکند!
return (
<MyContext.Provider value={{ state, setState }}>
{children}
</MyContext.Provider>
);
}
برای اجتناب از این trap روشهایی مانند memoization یا splitting context توصیه میشود، اما همچنان context API برای massive state کافی نیست.
🔗 مقاله مرجع برای مطالعهی دقیقتر:
https://redux.js.org/usage/when-should-i-use-redux
#React #Redux #Zustand #Jotai #Architecture
Advanced React Concepts
@DeveloperAdvocate 🥑1 035
آیا انتخاب استراتژی مناسب برای replication دیتابیس هنوز هم چالشآور است؟ بیایید سه معماری مرسوم را مرور کنیم تا بتوانید متناسب با نیازهای سیستم خود انتخاب بهتری داشته باشید:
🔸 Master-Slave Replication
یک سرور (Master) تمام عملیات Write را دریافت میکند و سایر سرورها (Slaves) صرفاً کار Read را انجام میدهند. این الگو latency نوشتن را پایین نگه میدارد و مقیاسپذیری خواندن را با اضافه کردن Slaves افزایش میدهد. اما دقت کنید که failover کمی پیچیده است و در زمان fail شدن Master، نوشتن متوقف میشود.
🔸 Master-Master Replication
دو یا چند سرور قابلیت نوشتن و خواندن دارند. این ساختار، قابلیت دسترسپذیری بالا و فشار تقسیم شده بر روی شانه چند Master را میدهد؛ البته چالش اصلی آن conflict resolution است—زمانی که دو تغییر همزمان روی داده شود (write conflicts). استراتژیهایی مانند Last-Write-Wins یا custom conflict resolver component باید به دقت انتخاب شوند.
🔸 Group Replication (یا Multi-Master با Consensus)
در این الگو (شبیه Galera یا MySQL Group Replication)، همه nodeها همزمان میتوانند خواندن و نوشتن داشته باشند و تغییرات از طریق consensus (معمولاً با پروتکلهایی مانند Paxos یا Raft) هماهنگ میشوند. این رویکرد بسیار قوی برای availability و consistency است اما latency نوشتن و هزینه هماهنگی بالایی دارد. مناسب برای سیستمهایی است که RPO و RTO کم اهمیت بالایی دارند.
نکته: انتخاب ساختار replication مستقیماً بر الگوی failover، نحوه مدیریت conflict و consistency مدل سیستم اثر میگذارد. در .NET، پیادهسازی مکانیزمهایی نظیر eventual consistency و conflict resolution به تناسب مدل داده و نیازهای سرویس با delegation یا event sourcing قابل تقویت است.
نمونهای از کانفیگ ارتباط با Read Replicaها در EF Core:
optionsBuilder.UseSqlServer(
"Server=PrimaryDb;Database=AppDb;User Id=sa;Password=yourStrong(!)Password;",
sqlOptions => sqlOptions
.UseRelationalNulls()
.EnableRetryOnFailure(5)
.ExecutionStrategy(context =>
new ReadReplicaExecutionStrategy(context))
);
برای مطالعه دقیقتر در خصوص استراتژیها، توصیه میکنم مقاله Martin Fowler را از دست ندهید:
https://martinfowler.com/articles/patterns-of-distributed-systems/replicated-log.html
Advanced Database Concepts
@DeveloperAdvocate 🥑1 035
🚦 یکی از چالشهای رایج در تیمهای مهندسی، بروز تعارض میان اعضاست—بهخصوص زمانی که توسعهدهندگان ارشد با رویکردها و دیدگاههای متفاوت روبهرو میشوند. کلید یک معماری موفق، توانایی حل این تعارضها بهشکل ریشهای و فنی است، بدون اینکه به سلامت روانی تیم آسیب برسد یا خلاقیت سرکوب شود.
یک تکنیک کارآمد، استفاده از "ADR" (Architectural Decision Record) بهعنوان ابزار گفتگو است؛ بهجای بحث شفاهی یا ایمیلی، هر دو دیدگاه را با مستندات و استدلال فنی شفاف صورتبندی کنید. فرض کنید اختلافنظر درمورد استفاده از CQRS دارید. اعضا میتوانند هر سناریو را با مزایا/معایب فنی در یک ADR مکتوب کنند:
// مثال ثبت یک ADR ساده:
Title: Should we adopt CQRS for order management?
Context:
- High business complexity in order workflows
- Team has moderate CQRS experience
Decision:
- Proceed with CQRS implementation for Order aggregate
Consequences:
- Improves scalability, but adds learning curve
- Forces clear separation of concerns
این شفافسازی نوشتاری، فضا را از جدل شخصی به تحلیل متمرکز فنی منتقل میکند. تمایز میان "Core Value"ها و "Personal Preference"ها را همیشه برجسته کنید؛ نرمافزاری مقیاسپذیر وقتی بهدست میآید که منافع تیم و محصول بر سلایق فردی تقدم یابد.
اگر اختلاف حل نشد، از جلسههای "Tech Review" استفاده کنید و فقط صاحبان کد یا کسانی که درگیر دیپلوی آن سیستم هستند رأی دهند. تجربه نشان داده با این فرایند، هم مستندسازی تقویت میشود و هم حس همافزایی و چونوچرایی معماری افزایش مییابد.
مقاله پیشنهادی (Martin Fowler):
https://martinfowler.com/articles/adr.html
Technical Leadership & Mentoring
@DeveloperAdvocate 🥑1 035
✅ نگهداشتپذیری کد: عیار کدنویسی حرفهای
یکی از سرمایههای ناملموس هر محصول نرمافزاری، «نگهداشتپذیری» (Maintainability) آن است. این معیار تعیین میکند که چقدر بهراحتی میتوان کد را فهمید، توسعه داد و رفع اشکال کرد. سطح نگهداشتپذیری محصول مستقیماً بر هزینه، زمان و کیفیت آینده تأثیر میگذارد.
چند اصل پیشرفته برای ارتقای نگهداشتپذیری کد در پروژههای داتنت:
🔹 صراحت در نامگذاری: انتخاب نام متغیر، متد و کلاس باید مفهوم را بیابهام منتقل کند. نامهای کوتاه و مبهم قاتل نگهداشتپذیریاند.
// ضعیف
var d = DateTime.Now.Subtract(u.Birth);
// بهتر
var userAge = DateTime.Now.Subtract(user.BirthDate);
🔹 واحدسازی مسئولیتها (SRP): یک کلاس یا متد باید فقط یک مسئولیت پیشبینیشده داشته باشد. اگر متدی بیش از ۲۰ خط است، اغلب بوی مشکلات طراحی میدهد.
// نقض SRP
public void ProcessOrder(Order order)
{
Validate(order);
Save(order);
SendConfirmationEmail(order);
}
// رعایت SRP
public void Validate(Order order) { ... }
public void Save(Order order) { ... }
public void SendConfirmationEmail(Order order) { ... }
🔹 وابستگی به انتزاع (Dependency Inversion/Abstraction over implementation):
// ضد نگهداشتپذیری
public class ReportGenerator
{
public void Generate()
{
var db = new SqlConnection();
//...
}
}
// بهتر
public class ReportGenerator
{
private readonly IDataStore _dataStore;
public ReportGenerator(IDataStore dataStore) => _dataStore = dataStore;
}
🔹 کپسولهسازی منطق پیچیده: توابع یا کلاسهای خاص برای مدیریت رفتارهای پیچیده و جلوگیری از گسترش منطق در کل پروژه بسازید.
🔹 مستندسازی حداقلی و هدفمند: نیازی به مستندسازی همهچیز نیست؛ کد تمیز، خودش مستند است. ولی در موارد پیچیده یا زمانی که منطق خاصی اعمال شده باید در کامنتها دلایل، نه صرفاً چگونگی، را بنویسید.
🔹 الگوهای طراحی مناسب: استفاده از الگوهایی مثل Strategy، Mediator یا Decorator، نگهداشتپذیری را بهشکل معناداری افزایش میدهد.
👁🗨 نگهداشتپذیری را نه یک ویژگی لوکس، بلکه یک اصل معماری بدانید؛ معماریای که تضمینکننده عمر نرمافزار شماست.
مطالعه بیشتر (Martin Fowler – Code Readability):
https://martinfowler.com/bliki/CodeReadability.html
Quality Attributes & Cross-Cutting Concerns
@DeveloperAdvocate 🥑1 035
در TypeScript، مفهوم Advanced Types قدرت زیادی برای طراحی مدلهای دادهای منعطف و ایمن به ما میدهد—چیزی که در معماریهای مبتنی بر #DomainDrivenDesign و APIهایی با نیازمندیهای بالا غیرقابل اغماض است. اما استفاده هوشمندانه از Utility Types، Conditional Types و Mapped Types میتواند پیچیدگی را مهار و قابلیت نگهداری کد را چند برابر کند.
🚀 چطور؟ فرض کنید با یک مدل داده بزرگ سروکار دارید و میخواهید انواع مختلفی derive کنید—مثلاً فقط بخشهای خواندنی، یا فقط آنهایی که قابل ویرایشاند، یا پروجکشن کاملاً متفاوتی بر اساس شرط. به چند نمونه کاربردی نگاه کنیم:
type ApiResponse<T> = T extends Array<infer U>
? { data: U[]; total: number }
: { data: T }; // Conditional Type
type ReadonlyPerson = Readonly<Person>; // Utility Type
type EditableFields<T> = {
[P in keyof T as P extends `edit${string}` ? P: never]: T[P]
}; // Mapped + Conditional Key Remapping
type Nullable<T> = { [P in keyof T]: T[P] | null };
این قدرت، امکان نوشتن DSLهای انعطافپذیر و قراردادهای دقیق بین Frontend و Backend را فراهم میکند که با TypeScript، Domain Modelهایتان تقریباً به همان وضوح و صراحتی که در C# میسازید—مثلاً با استفاده از Generics و Reflection—قابل پیادهسازی میشوند.
⛓ برای عمیقتر شدن و تسلط کامل بر این مفاهیم، این مقالهی عمیق از Microsoft Docs را از دست ندهید:
https://learn.microsoft.com/en-us/typescript/handbook/2/types-from-types.html
#TypeScript #AdvancedTypes #UtilityTypes #MappedTypes #ConditionalTypes #SoftwareArchitecture
Advanced JavaScript/TypeScript
@DeveloperAdvocate 🥑1 035
🎯 یادگیری مؤثر زبانها و فریمورکهای جدید: رویکرد معماری
یادگیری یک زبان یا فریمورک جدید صرفاً به مطالعهی syntax ختم نمیشود؛ هدف، درک عمیق مفاهیم انتزاعی و چگونگی ادغام آنها در معماری پروژههای واقعی است. برای مهندسین ارشد، استراتژیهای زیر میتواند تحول جدی در روند یادگیری ایجاد کند:
1️⃣ مدل ذهنی و تطابق مفاهیم: مفاهیم کلیدی را با دانستههای قبلی خود نگاشت کنید. مثلاً اگر از داتنت به Node.js مهاجرت میکنید، DI، مدل async، و ارکسترشِن dependencyها را مقایسه و تطبیق دهید.
2️⃣ پروژه نمونه اما واقعی: بهجای حل تمرینهای ناقص، یک ماژول کوچک از پروژه فعلی خود (مثلاً Authentication یا Event-Driven Integration) را با فناوری جدید پیادهسازی کنید تا مشکلات عملی مثل migration data، و versioning را تجربه کنید.
3️⃣ کدنویسی تستمحور (TDD): به جای نوشتن تست بعد از کد، از ابتدا با فرض وجود تستها ساختار فریمورک را بشناسید تا نظم معماری به شکل عمیقتری در ذهن شما نقش ببندد:
// نمونه TDD برای یک MarkdownParser در #C#
public interface IMarkdownParser
{
string ToHtml(string markdown);
}
public class MarkdownParser : IMarkdownParser
{
public string ToHtml(string markdown)
{
// فرض بر ادغام با فریمورک جدیدمثلاً Markdig یا هر ابزار متنباز دیگر
return Markdig.Markdown.ToHtml(markdown);
}
}
// تست نمونه با xUnit
public class MarkdownParserTests
{
[Fact]
public void ToHtml_ParsesHeadersCorrectly()
{
// Arrange
IMarkdownParser parser = new MarkdownParser();
string input = "# عنوان";
// Act
string result = parser.ToHtml(input);
// Assert
Assert.Contains("<h1>", result);
}
}
4️⃣ مطالعه معماری Reference: تحلیل پروژههای متنباز بالغ (مثلاً OrchardCore برای .NET یا حتی پروژههای مطرح Go و Node) دیدگاه معماری زبان جدید را به شما القا میکند.
5️⃣ Mirror Coding: یک ویژگی را با دو فناوری مختلف (مثل EF Core و Dapper یا حتی Dapper و MongoDB.Driver) پیاده کنید تا تفاوت Layering و رویکردهای Data Access ملموس شود.
6️⃣ تعامل فعال با منابع رسمی: همیشه تغذیه اطلاعات خود را از مستندات رسمی (docs) آغاز کنید؛ و پیگیر RFC، issueهای Github و devblogهای رسمی باشید.
📝 مقاله پیشنهادی از Martin Fowler:
https://martinfowler.com/articles/language-learning.html
در این مقاله، فاولر درباره استراتژیهای یادگیری زبان برنامهنویسی و انتقال مفاهیم معماری صحبت میکند—خواندنش برای هر معمار نرمافزار، توصیه میشود.
Productivity & Self-Improvement
@DeveloperAdvocate 🥑1 035
🔐 یکپارچگی با ارائهدهندگان هویت خارجی: Okta، Auth0 و Azure AD B2C در معماریهای مدرن
مدیریت هویتِ کاربران در راهکارهای مقیاسپذیر امروزی، چیزی فراتر از یک دیتابیس User استاندارد است. سرویسهایی مانند Okta، Auth0 و Azure AD B2C نهتنها گواهی احراز هویت مدرن (OIDC/SAML) را ارائه میدهند، بلکه قابلیتهایی چون MFA، Federation، و Lifecycle Management را به صورت plug&play در اختیار شما میگذارند.
در .NET، پیادهسازی این ادغام با استفاده از
OpenIdConnect در Authentication Middleware بسیار تمیز و قابل مدیریت است. مثال زیر نحوه اتصال به Azure AD B2C را با حداقل تنظیمات در Startup.cs نشان میدهد:
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
options.Authority = $"https://{yourB2CDomain}.b2clogin.com/{yourB2CDomain}.onmicrosoft.com/{yourB2CPolicy}/v2.0/";
options.ClientId = "your-client-id";
options.ClientSecret = "your-client-secret";
options.ResponseType = "code";
options.SaveTokens = true;
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("offline_access");
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name"
};
});
🔸 Best Practices
- ⚡ Decouple Identity from Business Logic: فرآیند احراز هویت را کاملاً از سرویسهای اپلیکیشن جدا کنید و فقط Claims موردنیاز را inject نمایید.
- 🔥 Centralized Identity Management: اجازه دهید تمام مدیریت کاربران، MFA، Password Reset و غیره، فقط در IdP انجام شود. اپلیکیشنها را stateless و ساده نگه دارید.
- 🛡 Security: حتماً از PKCE پشتیبانی کنید و Implicit Flow را کنار بگذارید. MFA/Conditional Access در IdP فعال باشد.
برای مطالعه عمیقتر و بررسی نکات و pitfalls عملی، این مقاله Microsoft را از دست ندهید:
https://learn.microsoft.com/en-us/azure/active-directory-b2c/identity-provider-integration-overview
#IdentityManagement #OpenIDConnect #AzureADB2C #Okta #Auth0 #SoftwareArchitecture
Application Security (AppSec)
@DeveloperAdvocate 🥑1 035
✦ اصل معماری Zero-Trust: امنیتی که باید پیشفرض باشد
معماری Zero-Trust بر این فرض استوار است که هیچ هویتی یا ترافیکی را نباید، فارغ از منشأ آن (داخل یا خارج)، مورد اعتماد قرار داد. در .NET میتوان این رویکرد را در سطوح مختلف پیادهسازی کرد: از فیلترهای احراز هویت و تفویض اختیار در API گرفته تا کنترل عمیقتر سطح ارتباط بین سرویسها (Service-to-Service).
در ASP.NET Core یکی از راهکارهای عملی این است که تمام endpointها به طور پیشفرض Authorize هستند و فقط در موارد خاص مجوز دسترسی داده میشود. این نکتهای حیاتی است: اجازهی دسترسی باید استثنا باشد، نه قاعده. پیادهسازی:
// Program.cs
builder.Services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});
در این مثال با FallbackPolicy، تمام درخواستها بدون احراز هویت مسدود خواهند شد؛ مگر آنکه صراحتاً AllowAnonymous تعیین شود. این رفتار همچنین مانع حملات کلاسیک “Privilege Escalation” و “Endpoint Exposure“ خواهد شد.
همچنین برای بین سرویسها (مثلاً در معماری Microservices)، پیشنهاد میشود هر سرویس، توکن JWT را مستقل اعتبارسنجی کند و به هر داده یا کد منبع خارجی شک کند. هرگز بر فرضیات شبکه داخلی تکیه نکنید؛ اصول Zero-Trust فراسیستمیاند.
مطالعهی تکمیلی و معتبر:
https://docs.microsoft.com/en-us/security/zero-trust/develop/securing-apps
Modern Architectural Patterns
@DeveloperAdvocate 🥑1 035
Dependency Injection در ASP.NET Core: سناریوهای پیشرفته و دامها 🚦
درک درست lifetimesها (Singleton, Scoped, Transient) برای جلوگیری از باگهای پنهان ضروریه. مثلا ثبت یک DbContext به عنوان Singleton میتونه فاجعه بسازه!
اما پیچیدگی وقتی شروع میشه که سرویسها به صورت زنجیرهای با هم وابستهاند یا فکتوریهای شخصیسازی شده میسازیم.
🔹 Lifetimes و تداخل آنها
یک anti-pattern رایج، وابستهکردن Scoped یا Transient به Singleton است. این کار میتونه باعث نگهداری نادرست state یا memory leak بشه.
مثال چگونه یک Singleton به اشتباه یک سرویس Scoped را inject میکند:
services.AddScoped<IMyService, MyService>();
services.AddSingleton<MySingleton>();
public class MySingleton
{
public MySingleton(IMyService myService) { ... }
}
در این حالت، IMyService تنها یک بار ساخته شده و تا پایان عمر اپلیکیشن در Singleton میماند—این اتفاق دقیقا خلاف انتظار مدل Scoped است.
🔹 ساخت سرویس با factory
گاهی نیاز دارید سرویسهایی بسازید که پارامتر داینامیک میگیرند. رویکرد تمیز، استفاده از delegate factoryها است:
services.AddScoped<MyService>();
services.AddScoped<Func<int, MyService>>(provider => id =>
{
var service = provider.GetRequiredService<MyService>();
service.Id = id;
return service;
});
یا با استفاده از Microsoft.Extensions.DependencyInjection.Abstractions:
services.AddTransient<MyService>(provider =>
new MyService(DateTime.UtcNow)); // inject dependencies with dynamic values
🔹 بازبینی Anti-pattern ها
- Inject کردن IServiceProvider داخل سرویسها (Service Locator) 👎
- ساخت دستی اشیاء وابسته با new به جای DI 👎
- ثبت سرویسهای State-full به عنوان Singleton 👎
لینک مقاله پیشنهادی (بررسی عمیق lifetimes و pitfalls):
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-8.0#service-lifetimes
ASP.NET Core (Advanced)
@DeveloperAdvocate 🥑1 035
تکنیکهای موثر برای بیان بدهی فنی به ذینفعان کسبوکار 🚦
یکی از جدیترین چالشهای معماران نرمافزار، ترجمهی عوارض بدهی فنی به زبانِ کسبوکار و جلب توجه استیکهولدرهاست. اگر صرفاً با عباراتی مثل «کد تمیز نیست» یا «این بخش دچار کالچر دینی شده» صحبت کنیم، دغدغهی ما به ابعاد تجاری منتقل نمیشود. راهکار عملی: بدهی فنی را با مفاهیمی چون ریسک، هزینه، سرعت ارائه و قابلیت اعتماد ملموس کنید.
به عنوان مثال:
- «رفع این بدهی فنی، میزان خطاهای عملیاتی را تا ۳۰٪ کاهش میدهد و سرعت ارائه فیچر جدید را ۲۰٪ افزایش میدهد.»
- «تداوم وضعیت فعلی، خطر downtime غیرمنتظره در critical release را بالا میبرد.»
برای مستندسازی عملی تأثیر بدهی فنی، یک ماتریس ساده طراحی کنید:
public record TechnicalDebtItem(
string Name,
string BusinessRisk,
double DeliverySlowdownPercent,
double CostToFix,
string ReleaseImpact
);
var debts = new List<TechnicalDebtItem>
{
new("Auth Service Legacy", "Security Breach Risk", 25, 4, "Critical"),
new("Payment Gateway Adapter", "Delayed Launch", 15, 2, "High"),
// ...
};
foreach (var debt in debts)
{
Console.WriteLine(
$"Item: {debt.Name}\n" +
$"Risk: {debt.BusinessRisk}\n" +
$"Slowdown: {debt.DeliverySlowdownPercent}%\n" +
$"Fix Cost: {debt.CostToFix} dev-weeks\n" +
$"Impact: {debt.ReleaseImpact}\n");
}
این ساختار به شما امکان میدهد که تأثیر فنی را به معیارهای تجاری قابل فهم و تصمیمگیری ترجمه کنید.
📌 بیشتر بخوانید:
https://martinfowler.com/bliki/TechnicalDebtQuadrant.html
Technical Leadership & Mentoring
@DeveloperAdvocate 🥑1 035
🌐 ذخیره و جستوجوی Embeddingهای برداری در بانکهای اطلاعاتی: شاهراهی به هوش مصنوعی داده-محور
امروزه اکثر موتورهای Semantic Search، سیستمهای پیشنهاددهنده (Recommender)، و امکانات هوشمند در محصولات، متکی به Embedding برداری دادهها هستند—چه متن، چه تصویر یا حتی دادههای ترکیبی. حال سؤال: بردارهای چندصدبعدی را کجا و چگونه ذخیره کنیم که هم عملکرد خوبی بگیریم و هم بتوانیم جستوجو/تحلیل کارایی داشته باشیم؟
🔸 بانکهای اطلاعاتی Relational (مثل SQL Server، PostgreSQL):
در SQL Server 2022 به بعد، با معرفی Vector Extension، میتوانید بردارها را در ستونهای جدیدی از نوع
VECTOR ذخیره و برای nearest neighbor search استفاده کنید؛ در غیر این صورت، باید بردارها را به صورت VARBINARY یا nvarchar ذخیره و منطق فاصله (مثلاً Cosine Similarity) را خودتان داخل SQL یا C# بنویسید.
یک نمونه پیادهسازی فاصله کسینوسی داخلی با LINQ:
public static double CosineSimilarity(float[] v1, float[] v2)
{
var dot = v1.Zip(v2, (a, b) => a * b).Sum();
var normA = Math.Sqrt(v1.Sum(x => x * x));
var normB = Math.Sqrt(v2.Sum(x => x * x));
return dot / (normA * normB);
}
در Entity Framework Core (با Value Conversion) میتوانید بردارها را راحتتر map کنید و همراه کوئریها حتی Embedding را روی دادهها محاسبه کنید.
🔸 بانکهای برداری تخصصی (مثل Pinecone، Qdrant، Weaviate):
این دیتابیسها کاملاً برای سنجش Similarity و جستوجوی برداری طراحی شدهاند (بهخصوص در ابعاد بالا و دیتاستهای حجیم). معمولاً API/SDKهای C# هم دارند و قابلیتهایی چون جستوجوی تقریباً نزدیک (ANN)، shard کردن خودکار و شاخص گذاری سریع را ارائه میکنند.
🔸 هیبریدی:
الگوی پیشنهادی: متادیتا و دادههای تراکنشی را در RDBMS، ولی Embeddingها را در یک بانک برداری سریع ذخیره کنید. هم میتوانید از Integrationهای آماده (مثلاً Azure SQL + Azure AI Search) استفاده کنید و هم از طریق application logic از هر دو سرویس کوئری بگیرید.
📎 مقاله پیشنهادی:
Vector Search: What Is It and Why Does It Matter? (Microsoft Semantic Kernel Blog)
#معماری #AI #SemanticSearch #Database #DotNet
Advanced Database Concepts
@DeveloperAdvocate 🥑1 035
در سیستمهای enterprise، دغدغههایی مثل logging، caching، validation و security تقریباً همهجا حضور دارند (Cross-Cutting Concerns). اجرای این دغدغهها نباید منجر به تکرار کد یا پیچیدگی کلاسها شود. سه تکنیک اصلی برای جداسازی این concerns وجود دارد:
✅ Middleware: در ASP.NET Core، middlewareها هر درخواست را قبل/بعد از handler مدیریت میکنند. این الگو برای concerns سطح application مثل authentication، logging یا rate-limiting بسیار مناسب است.
// Sample logging middleware
public class LoggingMiddleware
{
private readonly RequestDelegate _next;
public LoggingMiddleware(RequestDelegate next) => _next = next;
public async Task InvokeAsync(HttpContext context)
{
Console.WriteLine($"Request: {context.Request.Path}");
await _next(context);
Console.WriteLine($"Response: {context.Response.StatusCode}");
}
}
در Startup:
app.UseMiddleware<LoggingMiddleware>();
---
✅ Decorators: Decorator Pattern مخصوصاً در dependency injection برای concernsهای کلاسهای service فوقالعاده کاربرد دارد. مثال: تزریق ICacheService، و دکوریت کردن آن برای کشینگ شفاف بدون دستکاری لایه اصلی.
public class CachingOrderService : IOrderService
{
private readonly IOrderService _inner;
private readonly IMemoryCache _cache;
public CachingOrderService(IOrderService inner, IMemoryCache cache)
{
_inner = inner; _cache = cache;
}
public Order GetOrder(int id)
{
return _cache.GetOrCreate($"Order_{id}", _ => _inner.GetOrder(id));
}
}
در DI:
services.Decorate<IOrderService, CachingOrderService>();
(برای این قابلیت میتوانید پکیج Scrutor را در نظر بگیرید.)
---
✅ Aspects (Aspect-Oriented Programming): AOP امکان تعریف concerns با granular ترین سطح (مثلاً لاگ کردن صرفاً متدهای خاص) را فراهم میکند. در داتنت، با toolsی مثل AspectInjector یا Castle.DynamicProxy میتوانید attributes اختصاصی ایجاد و logic خود را inject کنید.
[Log]
public void PlaceOrder(Order order)
{
// Only business logic here!
}
---
✳️ انتخاب هر رویکرد، به context و granularity نیاز شما بستگی دارد:
- میدلویرها برای concerns سراسری درخواستها.
- دکوریتورها برای wrapping سرویسها در لایه business.
- Aspects برای injection هوشمند concernها در متد یا کلاس خاص.
📚 مطالعه بیشتر:
Martin Fowler — Aspect-Oriented Programming
https://martinfowler.com/bliki/AspectOrientedProgramming.html
Quality Attributes & Cross-Cutting Concerns
@DeveloperAdvocate 🥑1 035
Typescript امروزه با انواع پیشرفته (Advanced Types) امکاناتی برای مدلسازیهای پیچیده و اطمینان از صحت کد فراهم میکند که معادل آن در زبانهایی مثل C#، معمولاً با استفاده از genericها و expressionهای پیچیدهتر بهدست میآید. سه دسته از انواع پیشرفته، معماری کد را متحول میکنند:
🔹 Utility Types – مشابه توابع کمکی reflection در C#. انواعی مانند
Partial<T>، Pick<T, K> یا Omit<T, K> به شما اجازه میدهند با سرعت و دقت، ساختارهای جدید بر اساس نوعهای موجود بسازید. برای مثال نمونهای مشابه نوع ناشناس با حذف یک property را در C# میتوان اینگونه مدل کرد:
public class User { public int Id { get; set; } public string Name { get; set; } public string? Email { get; set; } }
// حذف Email:
public class UserWithoutEmail { public int Id { get; set; } public string Name { get; set; } }
در TypeScript فقط با یک Omit ساده:
type UserWithoutEmail = Omit<User, 'Email'>;
🔹 Mapped Types – قدرت تطبیق داینامیک نوعها را فراهم میکنند. مشابه با تعریف template کلاسها در سطح کل ساختار، مثلاً اگر بخواهید همه فیلدها را اختیاری کنید (Partial)، یا آنها را با قید خاصی تطبیق بدهید:
// مدلسازی معادل تغییر داینامیک propertyها در C# اغلب پیچیده و مبتنی بر Reflection است.
در TypeScript:
type Partial<T> = { [P in keyof T]?: T[P] };
که خود ذاتاً یک mapped type است.
🔹 Conditional Types – معادل شرطیسازی genericها در C# (البته با سطح انتزاع کمتر)، توانایی تطبیق و فیلتر دقیق نوعها را به شما میدهد. مثلاً:
type NonNullable<T> = T extends null | undefined ? never : T;
در معماریهای پیشرفته TypeScript، این قابلیتها اساس safe API design و الگوهای برپایه type-level programming را میسازند، بهخصوص در طراحی SDKها، ORMها، یا تستهای هوشمند Compile-Time.
مطالعه بیشتر:
TypeScript Advanced Types: Utility, Conditional, and Mapped Types – devblogs.microsoft.com
Advanced JavaScript/TypeScript
@DeveloperAdvocate 🥑1 035
سلام رفقای گل ☕️🌟
اگه دوست داشتین ازم حمایت کنین و بهم انرژی بدین برای ادامه راه، میتونین از طریق لینک زیر یه قهوه مهمونم کنین:
💜 coffeebede.com/melad
حمایت شما باعث میشه با دلگرمی بیشتری تولید محتوا کنم و مسیرمو ادامه بدم 🙌
مرسی که کنارمی 💬❤️
