Concepts
Cratis Applications provides seamless integration between Cratis Concepts and MongoDB through automatic serialization support. Concepts are domain-driven design primitives that wrap primitive types with business meaning.
What are Concepts?
Section titled “What are Concepts?”Concepts are types that inherit from ConceptAs<T> and provide type-safe wrappers around primitive values:
public record UserId(Guid Value) : ConceptAs<Guid>(Value){ public static readonly UserId NotSet = new(Guid.Empty); public static implicit operator UserId(Guid value) => new(value); public static UserId New() => new(Guid.NewGuid());}
public record ProductName(string Value) : ConceptAs<string>(Value){ public static readonly ProductName NotSet = new(string.Empty); public static implicit operator ProductName(string value) => new(value);}Automatic Serialization
Section titled “Automatic Serialization”When you call UseCratisMongoDB(), all types implementing ConceptAs<T> are automatically configured for MongoDB serialization through the ConceptSerializationProvider.
How It Works
Section titled “How It Works”The ConceptSerializer<T> handles the serialization by:
- Detection: Automatically detects types that implement
ConceptAs<T> - Unwrapping: Serializes only the underlying value, not the wrapper
- Type Safety: Ensures type validation during serialization/deserialization
- Performance: Optimized to avoid unnecessary object creation
Example Usage
Section titled “Example Usage”public class User{ public UserId Id { get; set; } public ProductName Name { get; set; } public EmailAddress Email { get; set; } public DateTimeOffset CreatedAt { get; set; }}
// Usagevar user = new User{ Id = UserId.New(), Name = "John Doe", // Implicit conversion Email = "john@example.com", CreatedAt = DateTimeOffset.Now};
// In MongoDB, this will be stored as:// {// "_id": "550e8400-e29b-41d4-a716-446655440000",// "name": "John Doe",// "email": "john@example.com",// "createdAt": ISODate("2024-01-15T10:30:00Z")// }Supported Underlying Types
Section titled “Supported Underlying Types”The ConceptSerializer<T> supports all primitive types that MongoDB can natively handle:
Numeric Types
Section titled “Numeric Types”int,uint,long,ulongfloat,double,decimalbyte,sbyte,short,ushort
String and Character Types
Section titled “String and Character Types”stringchar
Date and Time Types
Section titled “Date and Time Types”DateTimeDateTimeOffset(uses the custom serializer)DateOnly(uses the custom serializer)TimeOnly(uses the custom serializer)
Other Types
Section titled “Other Types”boolGuid- Any type that has a registered MongoDB serializer
Error Handling
Section titled “Error Handling”The concept serializer includes robust error handling:
Type Validation
Section titled “Type Validation”// This will throw TypeIsNotAConcept exceptionvar serializer = new ConceptSerializer<string>(); // Invalid - string is not a conceptNull Safety
Section titled “Null Safety”public record OptionalId(Guid? Value) : ConceptAs<Guid?>(Value){ public static readonly OptionalId NotSet = new(null);}
// Properly handles null values during serializationPerformance Benefits
Section titled “Performance Benefits”Storage Efficiency
Section titled “Storage Efficiency”Concepts are serialized as their underlying values, meaning:
- No wrapper overhead: Only the business value is stored
- Native MongoDB types: Uses optimal BSON types for each primitive
- Index compatibility: Underlying values can be indexed normally
Memory Efficiency
Section titled “Memory Efficiency”- Lazy initialization: Concept instances are created only when needed
- Value semantics: Record-based concepts minimize allocation overhead
- Implicit conversions: Reduce explicit casting requirements
Best Practices
Section titled “Best Practices”Use Static Members for Common Values
Section titled “Use Static Members for Common Values”public record Status(string Value) : ConceptAs<string>(Value){ public static readonly Status Active = new("Active"); public static readonly Status Inactive = new("Inactive"); public static readonly Status Pending = new("Pending");
public static implicit operator Status(string value) => new(value);}Implement Validation
Section titled “Implement Validation”public record EmailAddress(string Value) : ConceptAs<string>(Value){ public EmailAddress(string value) : this(Validate(value)) { }
static string Validate(string email) { if (string.IsNullOrWhiteSpace(email)) throw new ArgumentException("Email cannot be empty");
if (!email.Contains('@')) throw new ArgumentException("Invalid email format");
return email; }
public static implicit operator EmailAddress(string value) => new(value);}Event Source Integration
Section titled “Event Source Integration”For concepts used as Event Source IDs:
public record CustomerId(Guid Value) : ConceptAs<Guid>(Value){ public static readonly CustomerId NotSet = new(Guid.Empty); public static implicit operator CustomerId(Guid value) => new(value); public static implicit operator EventSourceId(CustomerId id) => new(id.Value.ToString()); public static CustomerId New() => new(Guid.NewGuid());}Collection Examples
Section titled “Collection Examples”Concepts work seamlessly in collections:
public class Order{ public OrderId Id { get; set; } public CustomerId CustomerId { get; set; } public IEnumerable<ProductId> ProductIds { get; set; } public Dictionary<ProductId, Quantity> Products { get; set; }}
// All concept types in collections are automatically handledQuery Support
Section titled “Query Support”Concepts can be used directly in MongoDB queries:
var customerId = CustomerId.New();var orders = await collection .Find(o => o.CustomerId == customerId) .ToListAsync();
// The concept is automatically converted to its underlying value for the queryNext Steps
Section titled “Next Steps”- Learn about Class Mapping for complex type configurations
- Explore Convention Packs for customizing serialization behavior
- Read about Naming Policies for consistent field naming