IronAlpine.Framework.Kernel
1.0.2
See the version list below for details.
dotnet add package IronAlpine.Framework.Kernel --version 1.0.2
NuGet\Install-Package IronAlpine.Framework.Kernel -Version 1.0.2
<PackageReference Include="IronAlpine.Framework.Kernel" Version="1.0.2" />
<PackageVersion Include="IronAlpine.Framework.Kernel" Version="1.0.2" />
<PackageReference Include="IronAlpine.Framework.Kernel" />
paket add IronAlpine.Framework.Kernel --version 1.0.2
#r "nuget: IronAlpine.Framework.Kernel, 1.0.2"
#:package IronAlpine.Framework.Kernel@1.0.2
#addin nuget:?package=IronAlpine.Framework.Kernel&version=1.0.2
#tool nuget:?package=IronAlpine.Framework.Kernel&version=1.0.2
IronAlpine.Kernel v4.0.0
Enterprise-grade core domain primitives for DDD microservices — zero external dependencies, 100% source protected.
- Version: 4.0.0
- Target Frameworks: net9.0, net10.0
- External Dependencies: ZERO (only BCL)
- Package Size: ~55 KB
- License: MIT
- NuGet: https://www.nuget.org/packages/IronAlpine.Kernel
Overview
IronAlpine.Kernel is the foundational layer for all IronAlpine-based systems. It provides battle-tested domain primitives used across distributed microservices. Zero external dependencies means you can use Kernel in any project without adding transitive dependencies.
Why IronAlpine.Kernel?
- ✅ Zero dependencies — use in UI, API, CLI, libraries without dependency bloat
- ✅ Proven patterns — Entity, AggregateRoot, ValueObject, DomainEvent
- ✅ Type safety — Result<T> pattern eliminates null reference exceptions
- ✅ Testable — all types are mockable, no static methods
- ✅ Source protected — all implementations internal, interfaces public
- ✅ Performance — minimal overhead, no reflection, no serialization
- ✅ Multi-framework — net9.0 & net10.0 support
Installation
dotnet add package IronAlpine.Kernel
Core Types Reference
1. Entity & Identity
What it is: Base class for objects with persistent identity (not value-based equality)
// Domain layer
public class TimeOff : Entity
{
public TimeOffId Id { get; private set; }
public UserId RequestedBy { get; private set; }
public DateTime StartDate { get; private set; }
public DateTime EndDate { get; private set; }
public TimeOffStatus Status { get; private set; }
// Constructor for creating new instance
public static TimeOff Request(TimeOffId id, UserId userId, DateTime startDate, DateTime endDate)
{
if (startDate >= endDate)
throw new DomainException("StartDate must be before EndDate");
return new TimeOff
{
Id = id,
RequestedBy = userId,
StartDate = startDate,
EndDate = endDate,
Status = TimeOffStatus.Pending
};
}
// Domain methods that change state
public void Approve(UserId approver, string comment)
{
if (Status != TimeOffStatus.Pending)
throw new DomainException($"Cannot approve {Status} request");
Status = TimeOffStatus.Approved;
// More logic...
}
public void Reject(UserId rejector, string reason)
{
if (Status != TimeOffStatus.Pending)
throw new DomainException("Can only reject pending requests");
Status = TimeOffStatus.Rejected;
// More logic...
}
}
// Identity value object
public class TimeOffId : ValueObject
{
public Guid Value { get; }
public TimeOffId(Guid value)
{
if (value == Guid.Empty)
throw new ArgumentException("TimeOffId cannot be empty");
Value = value;
}
protected override IEnumerable<object> GetAtomicValues()
{
yield return Value;
}
public override string ToString() => Value.ToString();
}
Key characteristics:
- Identity-based equality (two entities with same Id are equal)
- Can have mutable state (unlike value objects)
- Lifecycle: created, modified, deleted
- Used for business logic concentration
When to use:
- Objects with persistent identity (User, Order, TimeOff)
- Objects with changing state (Status, Amount, Name)
- Objects that exist in a repository
When NOT to use:
- Immutable data (use ValueObject instead)
- Data without identity (use records/DTOs)
2. AggregateRoot
What it is: Special Entity that is a consistency boundary (contains related entities)
// An aggregate root owns related entities
public class Order : AggregateRoot
{
private readonly List<OrderLine> _lines = new();
public OrderId Id { get; private set; }
public CustomerId CustomerId { get; private set; }
public OrderStatus Status { get; private set; }
public Money Total { get; private set; }
public IReadOnlyList<OrderLine> Lines => _lines.AsReadOnly();
// IMPORTANT: Private setters, exposed through methods only
// This prevents bypassing business logic
public static Order CreateForCustomer(OrderId id, CustomerId customerId)
{
var order = new Order
{
Id = id,
CustomerId = customerId,
Status = OrderStatus.Created,
Total = Money.Zero
};
// Publish domain event when aggregate is created
order.AddDomainEvent(new OrderCreatedEvent(id, customerId));
return order;
}
// Business methods on aggregate
public void AddLine(OrderLine line)
{
if (Status != OrderStatus.Created && Status != OrderStatus.Pending)
throw new DomainException("Cannot add lines to finalized order");
_lines.Add(line);
RecalculateTotal();
// Publish event
this.AddDomainEvent(new LineAddedToOrderEvent(this.Id, line.ProductId, line.Quantity));
}
public void ConfirmOrder()
{
if (_lines.Count == 0)
throw new DomainException("Order must have at least one line");
Status = OrderStatus.Confirmed;
this.AddDomainEvent(new OrderConfirmedEvent(this.Id, this.Total));
}
public void CancelOrder(string reason)
{
if (Status == OrderStatus.Cancelled || Status == OrderStatus.Shipped)
throw new DomainException("Cannot cancel finalized order");
Status = OrderStatus.Cancelled;
this.AddDomainEvent(new OrderCancelledEvent(this.Id, reason));
}
private void RecalculateTotal()
{
Total = _lines.Aggregate(Money.Zero, (acc, line) => acc + line.Total);
}
// Related entity (part of aggregate, never exposed to external callers)
public class OrderLine : Entity
{
public OrderLineId Id { get; private set; }
public ProductId ProductId { get; private set; }
public int Quantity { get; private set; }
public Money UnitPrice { get; private set; }
public Money Total => UnitPrice.MultiplyBy(Quantity);
public void UpdateQuantity(int newQuantity)
{
if (newQuantity <= 0)
throw new DomainException("Quantity must be greater than 0");
Quantity = newQuantity;
}
}
}
Key characteristics:
- Root entity that owns a cluster of related entities
- Enforces invariants for entire cluster
- External callers reference only the root
- Internal entities not exposed (List<OrderLine> is private, accessed via methods)
- Publishes domain events on state changes
Consistency boundaries example:
Order (Aggregate Root)
├── OrderLine (internal entity)
├── OrderLine (internal entity)
└── OrderLine (internal entity)
// Valid: reference Order
var order = repository.GetById(orderId);
// WRONG: never do this (orderId cannot reference OrderLine)
var line = repository.GetById(lineId); // ❌ Breaks aggregate boundary
Rules for aggregates:
- ✅ Reference the root only
- ✅ Keep aggregates small (5-20 entities max)
- ✅ Communicate between aggregates via events
- ✅ One transaction per aggregate operation
- ❌ Don't fetch sub-entities independently
- ❌ Don't expose child collections directly
3. ValueObject
What it is: Immutable object compared by all its properties (not identity)
// ValueObject is compared by VALUE, not ID
public class Money : ValueObject
{
public decimal Amount { get; }
public Currency Currency { get; }
public Money(decimal amount, Currency currency)
{
if (amount < 0)
throw new ArgumentException("Amount cannot be negative");
Amount = amount;
Currency = currency;
}
public Money Add(Money other)
{
if (other.Currency != this.Currency)
throw new DomainException($"Cannot add {other.Currency} to {this.Currency}");
return new Money(this.Amount + other.Amount, this.Currency);
}
public Money Multiply(decimal factor)
{
if (factor < 0)
throw new DomainException("Factor must be non-negative");
return new Money(this.Amount * factor, this.Currency);
}
// CRITICAL: Implement GetAtomicValues for equality
protected override IEnumerable<object> GetAtomicValues()
{
yield return Amount;
yield return Currency;
}
public override string ToString() => $"{Amount:C} {Currency}";
// Static factory methods for common values
public static Money Zero => new(0, Currency.USD);
public static Money FromUSD(decimal amount) => new(amount, Currency.USD);
}
public enum Currency
{
USD = 1,
EUR = 2,
GBP = 3,
TRY = 4
}
// Usage example
var price1 = new Money(100, Currency.USD);
var price2 = new Money(100, Currency.USD);
// TRUE: Same value means equality
Assert.AreEqual(price1, price2); // ✓
// TRUE: Creating a new instance still equals the same value
var price3 = new Money(100, Currency.USD);
Assert.AreEqual(price1, price3); // ✓
// Different currencies are NOT equal
var price4 = new Money(100, Currency.EUR);
Assert.AreNotEqual(price1, price4); // ✓
Characteristics:
- Immutable (properties readonly after construction)
- Compared by value (100 USD = 100 USD always)
- No identity (no ID needed)
- Reusable across aggregates
- Type-safe compared to primitives
ValueObject best practices:
// ✅ GOOD: Type-safe, business meaning
public Money UnitPrice { get; }
// ❌ BAD: Loose typing, unclear meaning
public decimal UnitPrice { get; }
// ✅ GOOD: Immutable, predictable
var newPrice = price.Multiply(1.1);
// ❌ BAD: Mutation, hard to track
price.Amount *= 1.1;
4. IDomainEvent
What it is: Immutable record of something important that happened in the domain
// Domain event - records WHAT happened
public class OrderConfirmedEvent : IDomainEvent
{
public OrderId OrderId { get; init; }
public Money TotalAmount { get; init; }
public UserId ConfirmedBy { get; init; }
public DateTime ConfirmedAt { get; init; }
public OrderConfirmedEvent(OrderId orderId, Money totalAmount, UserId confirmedBy = null)
{
OrderId = orderId ?? throw new ArgumentNullException(nameof(orderId));
TotalAmount = totalAmount ?? throw new ArgumentNullException(nameof(totalAmount));
ConfirmedBy = confirmedBy;
ConfirmedAt = DateTime.UtcNow;
}
}
// Another event
public class OrderShippedEvent : IDomainEvent
{
public OrderId OrderId { get; init; }
public string TrackingNumber { get; init; }
public DateTime ShippedAt { get; init; }
}
// Aggregate publishes events
public class Order : AggregateRoot
{
public void Confirm()
{
if (Status != OrderStatus.Pending)
throw new DomainException("Can only confirm pending orders");
Status = OrderStatus.Confirmed;
// Publish domain event - notify other aggregates/services
this.AddDomainEvent(new OrderConfirmedEvent(this.Id, this.Total));
}
public void Ship(string trackingNumber)
{
Status = OrderStatus.Shipped;
this.AddDomainEvent(new OrderShippedEvent(this.Id, trackingNumber));
}
}
// Accessing domain events
var order = await repository.GetByIdAsync(orderId);
var events = order.DomainEvents; // Get published events
// Events are persisted & published to message bus (Kafka, RabbitMQ)
// Other services subscribe:
// - Notification service sends email when order confirmed
// - Fulfillment service prepares shipment
// - Accounting service records revenue
// - Analytics service tracks order metrics
Event naming convention:
- ✅ Past tense: OrderConfirmed, PaymentProcessed, UserRegistered
- ✅ Business terminology: TimeOffApproved, InventoryReserved
- ❌ Technical: EntityChanged, DataUpdated
Domain events solve:
- ✅ Loose coupling between services
- ✅ Audit trail (what happened and when)
- ✅ Side effects (send email, update cache, etc.)
- ✅ Event sourcing patterns
- ✅ Saga patterns for distributed transactions
5. Guard Clauses
What it is: Defensive programming helpers for preconditions
// Guard usage in domain methods
public class TimeOff : AggregateRoot
{
public void Request(UserId requestedBy, DateTime startDate, DateTime endDate)
{
// Guard against null/invalid inputs
Guard.Against.Null(requestedBy, nameof(requestedBy));
Guard.Against.InvalidData(startDate < DateTime.Today, "Start date must be in future");
Guard.Against.InvalidData(startDate >= endDate, "Start must be before end");
Guard.Against.InvalidData((endDate - startDate).Days > 30, "Cannot request more than 30 days");
RequestedBy = requestedBy;
StartDate = startDate;
EndDate = endDate;
Status = TimeOffStatus.Pending;
}
public void Approve(UserId approver, string comment)
{
Guard.Against.Null(approver, nameof(approver));
Guard.Against.InvalidState(Status != TimeOffStatus.Pending, "Can only approve pending requests");
Guard.Against.NullOrEmpty(comment, nameof(comment));
Guard.Against.InvalidData(comment.Length > 500, "Comment too long");
Status = TimeOffStatus.Approved;
ApprovedBy = approver;
ApprovedAt = DateTime.UtcNow;
Comment = comment;
}
}
Guard methods available:
Guard.Against.Null(value, paramName)— throw if nullGuard.Against.NullOrEmpty(string)— throw if null/emptyGuard.Against.InvalidData(condition, message)— throw if condition trueGuard.Against.InvalidState(condition, message)— throw if invariant violatedGuard.Against.OutOfRange(value, min, max)— throw if out of range
6. Result<T> Pattern
What it is: Railway-oriented programming - explicit success/failure path
// Instead of throwing exceptions, return Result
public class TimeOffService
{
public Result<TimeOff> RequestTimeOff(UserId userId, DateTime startDate, DateTime endDate)
{
// Validation failures return Result.Failure
if (startDate < DateTime.Today)
return Result.Failure<TimeOff>("Start date must be in future");
if (startDate >= endDate)
return Result.Failure<TimeOff>("Start date must be before end date");
// Business rule checks
var existingRequests = _repository.FindByUserAndDatesAsync(userId, startDate, endDate);
if (existingRequests.Any())
return Result.Failure<TimeOff>("Overlap with existing request");
// Success case returns Result.Success
var timeOff = TimeOff.Request(TimeOffId.New(), userId, startDate, endDate);
return Result.Success(timeOff);
}
}
// Usage in controller
[HttpPost("request")]
public async Task<IActionResult> Request(RequestTimeOffDto dto)
{
var result = _service.RequestTimeOff(
new UserId(dto.UserId),
dto.StartDate,
dto.EndDate);
// Explicit success/failure handling
if (result.IsSuccess)
{
await _repository.AddAsync(result.Value);
return CreatedAtAction(nameof(Get), new { id = result.Value.Id });
}
else
{
return BadRequest(new { error = result.Error });
}
}
// Can chain operations
var result = _service.ValidateRequest(dto)
.Then(validated => _service.ProcessRequest(validated))
.Then(processed => _repository.SaveAsync(processed))
.Tap(saved => _logger.LogInformation("Request saved: {Id}", saved.Id));
if (result.IsSuccess)
return Ok(result.Value);
else
return BadRequest(result.Error);
Result benefits:
- ✅ No exception overhead for business failures
- ✅ Explicit success/failure types
- ✅ Composable pipelines
- ✅ Clear intent (not an error if validation fails)
- ✅ Better performance (no stack trace)
7. Specification Pattern
What it is: Encapsulates query logic in reusable, composable specifications
// Define specification for complex queries
public class TimeOffPendingApprovalSpec : Specification<TimeOff>
{
public TimeOffPendingApprovalSpec(UserId approverUserId)
{
// Define query
Query
.Where(t => t.Status == TimeOffStatus.Pending)
.Where(t => t.ApproverId == approverUserId)
.OrderByDescending(t => t.RequestedAt)
.Take(50);
// Include related data
Query.Include(t => t.Requester)
.Include(t => t.Approver);
}
}
// Use in repository
public class TimeOffRepository : Repository<TimeOff>
{
public async Task<IEnumerable<TimeOff>> GetPendingForApprovalAsync(
UserId approverId,
CancellationToken ct)
{
var spec = new TimeOffPendingApprovalSpec(approverId);
return await ListAsync(spec, ct);
}
}
// Reuse across services
var pendingForMgr = await timeOffRepo.GetPendingForApprovalAsync(managerUserId, ct);
var pendingForHR = await timeOffRepo.GetPendingForApprovalAsync(hrUserId, ct);
// Create complex specifications
public class TimeOffReportSpec : Specification<TimeOff>
{
public TimeOffReportSpec(int year, int month)
{
Query
.Where(t => t.StartDate.Year == year && t.StartDate.Month == month)
.Where(t => t.Status == TimeOffStatus.Approved)
.OrderBy(t => t.RequestedBy)
.ThenBy(t => t.StartDate);
Query.Include(t => t.Requester);
}
}
Architecture Patterns
Domain-Driven Design (DDD)
┌─────────────────────────────────────────────────┐
│ Domain Layer (IronAlpine.Kernel) │
│ ┌───────────────────────────────────────────┐ │
│ │ Aggregates: Order, TimeOff, User │ │
│ │ Entities: OrderLine, TimeOffHistory │ │
│ │ ValueObjects: Money, DateRange, UserId │ │
│ │ Domain Events: OrderConfirmed, Approved │ │
│ │ Domain Services: PricingService, Rules │ │
│ └───────────────────────────────────────────┘ │
│ ↑ │
│ No dependencies on infrastructure │
└─────────────────────────────────────────────────┘
↑ ↑
│ │
┌────────────────┐ ┌──────────────────┐
│ Application │ │ Infrastructure │
│ (Mediator) │ │ (Data, EventBus) │
└────────────────┘ └──────────────────┘
Complete Examples
Example 1: TimeOff Domain
// src/Domain/TimeOff/TimeOffId.cs
public class TimeOffId : ValueObject
{
public Guid Value { get; }
public TimeOffId(Guid value)
{
Guard.Against.InvalidData(value == Guid.Empty, "TimeOffId cannot be empty");
Value = value;
}
protected override IEnumerable<object> GetAtomicValues()
{
yield return Value;
}
public static TimeOffId New() => new(Guid.NewGuid());
}
// src/Domain/TimeOff/TimeOffStatus.cs
public enum TimeOffStatus
{
Pending = 1,
Approved = 2,
Rejected = 3,
Cancelled = 4,
Completed = 5
}
// src/Domain/TimeOff/TimeOff.cs
public class TimeOff : AggregateRoot
{
public TimeOffId Id { get; private set; }
public UserId RequestedBy { get; private set; }
public DateTime StartDate { get; private set; }
public DateTime EndDate { get; private set; }
public TimeOffStatus Status { get; private set; }
public UserId ApprovedBy { get; private set; }
public string ApprovalComment { get; private set; }
public DateTime RequestedAt { get; private set; }
// Factory method
public static TimeOff Request(TimeOffId id, UserId userId, DateTime startDate, DateTime endDate)
{
Guard.Against.Null(id, nameof(id));
Guard.Against.Null(userId, nameof(userId));
Guard.Against.InvalidData(startDate < DateTime.Today, "Start date must be in future");
Guard.Against.InvalidData(startDate >= endDate, "Start must be before end");
Guard.Against.InvalidData((endDate - startDate).Days > 30, "Max 30 days");
var timeOff = new TimeOff
{
Id = id,
RequestedBy = userId,
StartDate = startDate,
EndDate = endDate,
Status = TimeOffStatus.Pending,
RequestedAt = DateTime.UtcNow
};
timeOff.AddDomainEvent(new TimeOffRequestedEvent(
id.Value,
userId.Value,
startDate,
endDate));
return timeOff;
}
// Domain method
public void Approve(UserId approver, string comment)
{
Guard.Against.Null(approver, nameof(approver));
Guard.Against.NullOrEmpty(comment, nameof(comment));
Guard.Against.InvalidState(Status != TimeOffStatus.Pending, "Only pending requests can be approved");
Guard.Against.InvalidData(comment.Length > 500, "Comment too long");
ApprovedBy = approver;
ApprovalComment = comment;
Status = TimeOffStatus.Approved;
this.AddDomainEvent(new TimeOffApprovedEvent(this.Id.Value, approver.Value, comment));
}
public void Reject(UserId rejector, string reason)
{
Guard.Against.Null(rejector, nameof(rejector));
Guard.Against.NullOrEmpty(reason, nameof(reason));
Guard.Against.InvalidState(Status != TimeOffStatus.Pending, "Only pending requests can be rejected");
Status = TimeOffStatus.Rejected;
ApprovedBy = rejector;
ApprovalComment = reason;
this.AddDomainEvent(new TimeOffRejectedEvent(this.Id.Value, rejector.Value, reason));
}
}
// Domain events
public class TimeOffRequestedEvent : IDomainEvent
{
public Guid TimeOffId { get; init; }
public Guid UserId { get; init; }
public DateTime StartDate { get; init; }
public DateTime EndDate { get; init; }
public TimeOffRequestedEvent(Guid timeOffId, Guid userId, DateTime start, DateTime end)
{
TimeOffId = timeOffId;
UserId = userId;
StartDate = start;
EndDate = end;
}
}
public class TimeOffApprovedEvent : IDomainEvent
{
public Guid TimeOffId { get; init; }
public Guid ApprovedBy { get; init; }
public string Comment { get; init; }
public TimeOffApprovedEvent(Guid timeOffId, Guid approvedBy, string comment)
{
TimeOffId = timeOffId;
ApprovedBy = approvedBy;
Comment = comment;
}
}
Best Practices
✅ DO
// 1. Use Entities for objects with identity
public class User : Entity { public UserId Id { get; } }
// 2. Use ValueObjects for business concepts
public class EmailAddress : ValueObject { }
// 3. Publish events from aggregates
public void Approve() { AddDomainEvent(new ApprovedEvent(...)); }
// 4. Use Guard for preconditions
Guard.Against.Null(value, nameof(value));
// 5. Keep aggregates small and focused
public class Order : AggregateRoot { /* ~100-200 lines */ }
// 6. Factory methods for creation
public static Order Create(...) { }
// 7. Use private setters
public UserId RequestedBy { get; private set; }
❌ DON'T
// 1. Don't expose internal collections
public List<OrderLine> Lines { get; set; } // ❌
// 2. Don't mutate from outside
order.Status = OrderStatus.Approved; // ❌
// 3. Don't put business logic in DTOs
public class OrderDto { public void Approve() { } } // ❌
// 4. Don't use entities as DTOs
public class Order : Entity { /* returned to API */ } // ❌
// 5. Don't forget domain events
var order = Create(...); // No events published ❌
// 6. Don't use primitives
public decimal Amount { get; } // Use Money instead
// 7. Don't mix domains
public User { public Order LastOrder { get; } } // Coupling
Testing
[TestClass]
public class TimeOffTests
{
[TestMethod]
public void Request_ValidData_CreatesTimeOff()
{
// Arrange
var id = TimeOffId.New();
var userId = new UserId(Guid.NewGuid());
var startDate = DateTime.Today.AddDays(7);
var endDate = startDate.AddDays(5);
// Act
var timeOff = TimeOff.Request(id, userId, startDate, endDate);
// Assert
Assert.AreEqual(TimeOffStatus.Pending, timeOff.Status);
Assert.AreEqual(userId, timeOff.RequestedBy);
Assert.AreEqual(1, timeOff.DomainEvents.Count);
Assert.IsInstanceOfType(timeOff.DomainEvents[0], typeof(TimeOffRequestedEvent));
}
[TestMethod]
[ExpectedException(typeof(DomainException))]
public void Request_InvalidDates_Throws()
{
var id = TimeOffId.New();
var userId = new UserId(Guid.NewGuid());
// Should fail: start >= end
TimeOff.Request(id, userId, DateTime.Today, DateTime.Today);
}
[TestMethod]
public void Approve_PendingStatus_Approves()
{
// Arrange
var timeOff = TimeOff.Request(TimeOffId.New(), new UserId(Guid.NewGuid()),
DateTime.Today.AddDays(7), DateTime.Today.AddDays(10));
var approverId = new UserId(Guid.NewGuid());
// Act
timeOff.Approve(approverId, "Approved");
// Assert
Assert.AreEqual(TimeOffStatus.Approved, timeOff.Status);
Assert.AreEqual(approverId, timeOff.ApprovedBy);
Assert.AreEqual(2, timeOff.DomainEvents.Count);
}
}
Performance Considerations
- Zero allocation for value objects: ValueObject comparison is fast (no reference equality)
- Domain events are in-memory: No I/O until SaveChangesAsync
- Entity identity is Guid: No database lookup for equality
- No reflection: All types are AOT-friendly
- Minimal overhead: Guard clauses compiled to IL efficiently
Integration with Other IronAlpine Packages
| Package | Integration | Purpose |
|---|---|---|
| Mediator | Handlers work with domain types | Commands/queries use Entity, ValueObject |
| Data | Repository<Entity> | Persistence of aggregates |
| EventBus | IDomainEvent published to Kafka | Event-driven architecture |
| Observability | Logging domain state | Audit trails of domain changes |
| Security | IAuditUser provides context | Who changed what |
Troubleshooting
Q: How do I compare entities?
A: Entities use identity-based equality. user1.Id == user2.Id ✓
Q: Can ValueObjects be null?
A: No, ValueObjects must be non-null. Use Money? amount for optional values.
Q: How do I update an entity?
A: Through domain methods only: entity.Method(), never direct property assignment.
Q: Where do I put business logic? A: In domain types (entities, aggregates), not in services or controllers.
Version History
| Version | Changes |
|---|---|
| 4.0.0 | Current release |
| 3.0.0 | Initial v3 release |
| 2.x | v2 packages (deprecated) |
Made with ❤️ for DDD-based microservices
MIT License | https://github.com/KerimDemirer/IronAlpine
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- No dependencies.
-
net9.0
- No dependencies.
NuGet packages (7)
Showing the top 5 NuGet packages that depend on IronAlpine.Framework.Kernel:
| Package | Downloads |
|---|---|
|
IronAlpine.Framework.Mediator
High-performance mediator runtime with request/response, notifications, streaming, and configurable behavior pipeline. Includes validation (FluentValidation) support. Caching behavior lives in IronAlpine.Framework.Caching. |
|
|
IronAlpine.Framework.Data
EFCore data stack for IronAlpine: repositories, interceptors (audit, soft-delete, versioning), transaction and auditing behaviors, and domain event dispatching. Unified entry point: AddIronAlpineData<TDbContext>. Repository base classes remain public extension points for custom repositories. |
|
|
IronAlpine.Framework.Web
ASP.NET Core defaults, Swagger, health checks, correlation middleware, and YARP gateway transform for IronAlpine microservices. Single entry point: AddIronAlpineWebDefaults + UseIronAlpineWebDefaults. All implementations internal. |
|
|
IronAlpine.Framework.EventBus
Kafka-backed event bus with outbox/inbox pattern, dead letter support, and replay functionality. Unified namespace: IronAlpine.EventBus. Kafka implementation is internal; only contracts and topology builder are public. SSL cert parameters fully configurable (SslCaLocation, SslCertLocation, SslKeyLocation, SslKeyPassword). |
|
|
IronAlpine.Framework.Security
JWT authentication, claim-based authorization, and EFCore permission store for IronAlpine microservices. Provides ICurrentUser, permission policy provider, security builder, and public authorization store contracts. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 3.1.1 | 293 | 10/21/2026 |
| 3.0.9 | 273 | 10/15/2026 |
| 3.0.8 | 175 | 10/13/2026 |
| 3.0.7 | 155 | 10/13/2026 |
| 3.0.6 | 166 | 10/9/2026 |
| 3.0.5 | 242 | 10/5/2026 |
| 3.0.4 | 258 | 10/2/2026 |
| 3.0.3 | 235 | 9/30/2026 |
| 3.0.2 | 1,139 | 7/22/2026 |
| 3.0.1 | 201 | 7/22/2026 |
| 3.0.0 | 417 | 7/20/2026 |
| 2.0.3 | 219 | 7/20/2026 |
| 2.0.2 | 253 | 7/13/2026 |
| 2.0.1 | 258 | 7/13/2026 |
| 2.0.0 | 237 | 7/13/2026 |
| 2.0.0-preview.2 | 103 | 7/13/2026 |
| 2.0.0-preview.1 | 108 | 7/6/2026 |
| 1.0.2 | 212 | 7/3/2026 |
| 1.0.1 | 199 | 7/3/2026 |
Stable mediator release with request/response, notification publish strategies, streaming, and dependency injection integration.