corecraft by
Nuget / site data
Details
Info
Name: corecraft
A core library to build cross-platform and highly customizable domain models
Author:
NuGet: https://www.nuget.org/packages/corecraft/
You can find more details at https://github.com/AlexNav73/CoreCraft
Original Readme
Introduction
CoreCraft
is a comprehensive toolkit designed to simplify domain modeling and data management in .NET applications. It offers a range of powerful features and functionalities that enable developers to build robust and scalable domain models.
Main Features
The CoreCraft
provides a wealth of features, including:
Automatic Domain Model Generation with Roslyn Source Generators:
CoreCraft
leverages Roslyn Source Generators to automatically generate domain models based on your schema. This automated process eliminates the need for manual coding, saving you time and effort. The generated models are accurate, consistent, and reflect the structure of your schema.Change Tracking:
CoreCraft
incorporates change tracking mechanisms that allow you to monitor modifications to your domain model. By tracking changes at a granular level,CoreCraft
notifies you of specific modifications, enabling you to respond effectively. This feature eliminates the need for manual change detection and parsing of the entire model.Undo/Redo Support:
CoreCraft
simplifies the implementation of undo and redo operations in your application. It provides built-in support for managing and reverting changes, giving users the ability to undo actions and redo them as needed.Data integrity:
CoreCraft
follows a command-based execution approach, where the domain model is read-only by default, and modifications are made through commands. When a command executes, it operates on a snapshot of the model, ensuring data integrity in case of exceptions during command execution.Persistence Options:
CoreCraft
offers seamless support for persisting your generated domain model. WithCoreCraft
, there's no need for additional code to handle persistence. It supports saving and loading the model's state to a SQLite database and JSON files. The toolkit takes care of the storage and retrieval process, making it convenient and hassle-free. Additionally,CoreCraft
allows for easy implementation of additional storage options, making it flexible to adapt to your specific requirements.Plugin Architecture Support:
CoreCraft
is well-suited for use in a plugin architecture. It provides the necessary abstractions and features to support modular development, allowing different plugins to contribute to the overall application state.Reactive Extensions (Rx.NET) Integration:
CoreCraft
incorporates Reactive Extensions (Rx.NET) to provide a flexible subscription mechanism. It utilizes theIObservable
andIObserver
interfaces, allowing you to leverage the power of Rx.NET for event-driven programming and reactive data processing. This integration enables you to easily subscribe to change events and apply custom logic using the extensive set of operators provided byRx.NET
.
CoreCraft
empowers developers to create robust and scalable domain models with ease. With automatic model generation, change tracking, persistence options, and support for undo/redo operations, CoreCraft
simplifies application state management and enhances the user experience.
NuGet Packages
CoreCraft is distributed as NuGet packages.
Package | Status |
---|---|
CoreCraft | |
CoreCraft.Generators | |
CoreCraft.Storage.SQLite | |
CoreCraft.Storage.Json |
Basic usage
The only thing is needed to start using the CoreCraft
toolkit is to define the schema for the domain model. Create a *.model.json
file that describes your entities, properties and their relations. Here's an example:
{
"shards": [
{
"name": "ToDo",
"entities": [
{
"name": "ToDoItem",
"properties": [
{ "name": "Name", "type": "string", "defaultValue": "string.Empty" }
]
}
],
"collections": [
{ "name": "Items", "entityType": "ToDoItem" }
],
"relations": []
}
]
}
And add the additional files entry to the project file:
<ItemGroup>
<AdditionalFiles Include="Model.model.json" />
</ItemGroup>
The model schema is the only piece needed to define data of your domain model. Everything else will be automatically generated by the CoreCraft.Generators
package.
Now, an instance of the domain model can be created using an instance of generated ToDoModelShard
class:
// Create an instance of the domain model
var model = new DomainModel(new[] { new ToDoModelShard() });
Note: instead of using
DomainModel
class directly, you can use build-in classes (AutoSaveDomainModel
,UndoRedoDomainModel
) or inherit from it and implement custom logic
Then we need to subscribe to the model changes by providing an event handler method to handle the collection changes.:
// Subscribe to Items collection change events
using var subscription = model.For<IToDoChangesFrame>()
.With(x => x.Items)
.Subscribe(OnItemChanged);
// Observe changes
void OnItemChanged(Change<ICollectionChangeSet<ToDoItem, ToDoItemProperties>> changes)
{
foreach (var c in changes.Hunk)
{
Console.WriteLine($"Entity [{c.Entity}] has been {c.Action}ed.");
Console.WriteLine($" Old data: {c.OldData}");
Console.WriteLine($" New data: {c.NewData}");
}
}
When subscription is done, let's execute a command to modify the model:
// Adds new item to the collection
model.Run<IMutableToDoModelShard>(
(shard, _) => shard.Items.Add(new() { Name = "test" }));
Save the domain model to an SQLite database file.
var storage = new SqliteStorage(Array.Empty<IMigration>());
model.Save(storage, "my_data.db");
Please refer to the documentation for comprehensive information on using the CoreCraft
toolkit and its features.
License
MIT.
About
Decomposing properties and class into Domain Models. Seems however too complicated to use
How to use
Example ( source csproj, source files )
- CSharp Project
- Json2Code.csproj
- Program.cs
- Person.model.json
This is the CSharp Project that references corecraft
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GX</CompilerGeneratedFilesOutputPath>
</PropertyGroup>
<ItemGroup>
<None Remove="Person.model.json" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="Person.model.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</AdditionalFiles>
</ItemGroup>
<ItemGroup>
<PackageReference Include="CoreCraft" Version="0.6.0" />
<PackageReference Include="CoreCraft.Generators" Version="0.6.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>
This is the use of corecraft in Json2Code.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GX</CompilerGeneratedFilesOutputPath>
</PropertyGroup>
<ItemGroup>
<None Remove="Person.model.json" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="Person.model.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</AdditionalFiles>
</ItemGroup>
<ItemGroup>
<PackageReference Include="CoreCraft" Version="0.6.0" />
<PackageReference Include="CoreCraft.Generators" Version="0.6.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>
This is the use of corecraft in Program.cs
using CoreCraft;
using CoreCraft.ChangesTracking;
using CoreCraft.Subscription;
using Json2Code.Person;
using Json2Code.Person.Entities;
var model = new DomainModel(new[] { new Json2Code.Person.PersonShardModelShard() });
// Subscribe to Items collection change events
using var subscription = model.For<IPersonShardChangesFrame>()
.With(x => x.Persons)
.Subscribe(OnItemChanged);
// Observe changes
void OnItemChanged(Change<ICollectionChangeSet<Person, PersonProperties>> changes)
{
foreach (var c in changes.Hunk)
{
Console.WriteLine($"Entity [{c.Entity}] has been {c.Action}ed.");
Console.WriteLine($" Old data: {c.OldData}");
Console.WriteLine($" New data: {c.NewData}");
}
}
await model.Run<IMutablePersonShardModelShard> (
(shard, _) =>
{
shard.Persons.Add(new() { FirstName = "A", LastName = "B" });
//shard.Persons.Remove(shard.Persons.First());
});
await model.Run<IMutablePersonShardModelShard>(
(shard, _) =>
{
shard.Persons.Modify(shard.Persons.First(), p => p with { FirstName = "C" });
});
await model.Run<IMutablePersonShardModelShard>(
(shard, _) =>
{
shard.Persons.Remove(shard.Persons.First());
});
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
This is the use of corecraft in Person.model.json
{
"shards": [
{
"name": "PersonShard",
"entities": [
{
"name": "Person",
"properties": [
{
"name": "FirstName",
"type": "string",
"defaultValue": "\"Andrei\""
},
{
"name": "LastName",
"type": "string",
"defaultValue": "\"Ignat\""
}
]
}
],
"collections": [
{
"name": "Persons",
"entityType": "Person"
}
],
"relations": []
}
]
}
Generated Files
Those are taken from $(BaseIntermediateOutputPath)\GX
- Person.g.cs
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by the tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
#nullable enable
namespace Json2Code.Person
{
using CoreCraft.Core;
using CoreCraft.ChangesTracking;
using CoreCraft.Persistence;
using Json2Code.Person.Entities;
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("C# Source Generator", "1.0.0.0")]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public interface IPersonShardModelShard : IModelShard
{
ICollection<Person, PersonProperties> Persons { get; }
}
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("C# Source Generator", "1.0.0.0")]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public interface IMutablePersonShardModelShard : IModelShard
{
IMutableCollection<Person, PersonProperties> Persons { get; }
}
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("C# Source Generator", "1.0.0.0")]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute()]
internal static class PersonShardModelShardInfo
{
public static readonly CollectionInfo PersonsInfo = new("PersonShard", "Persons", new PropertyInfo[] { new("FirstName", typeof(string), false), new("LastName", typeof(string), false) });
}
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("C# Source Generator", "1.0.0.0")]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute()]
internal sealed partial class PersonShardModelShard : IPersonShardModelShard
{
public const string PersonsId = "Json2Code.Person.PersonShard.Persons";
public PersonShardModelShard()
{
Persons = new Collection<Person, PersonProperties>(
PersonsId,
static id => new Person(id),
static () => new PersonProperties());
}
internal PersonShardModelShard(IMutablePersonShardModelShard mutable)
{
Persons = ((IMutableState<ICollection<Person, PersonProperties>>)mutable.Persons).AsReadOnly();
}
public ICollection<Person, PersonProperties> Persons { get; init; } = null!;
}
internal sealed partial class PersonShardModelShard : IReadOnlyState<IMutablePersonShardModelShard>
{
public IMutablePersonShardModelShard AsMutable(global::System.Collections.Generic.IEnumerable<IFeature> features)
{
var persons = (IMutableCollection<Person, PersonProperties>)Persons;
foreach (var feature in features)
{
persons = feature.Decorate(this, persons);
}
return new MutablePersonShardModelShard()
{
Persons = persons,
};
}
}
internal sealed partial class PersonShardModelShard : ICanBeSaved
{
public void Save(IRepository repository)
{
repository.Save(PersonShardModelShardInfo.PersonsInfo, Persons);
}
}
internal sealed partial class PersonShardModelShard : IFeatureContext
{
IChangesFrame IFeatureContext.GetOrAddFrame(IMutableModelChanges modelChanges)
{
return modelChanges.Register(static () => new PersonShardChangesFrame());
}
}
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("C# Source Generator", "1.0.0.0")]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public interface IPersonShardChangesFrame : IChangesFrame
{
ICollectionChangeSet<Person, PersonProperties> Persons { get; }
}
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("C# Source Generator", "1.0.0.0")]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute()]
internal sealed class PersonShardChangesFrame : IPersonShardChangesFrame, IChangesFrameEx, ICanBeSaved
{
public PersonShardChangesFrame()
{
Persons = new CollectionChangeSet<Person, PersonProperties>(PersonShardModelShard.PersonsId);
}
public ICollectionChangeSet<Person, PersonProperties> Persons { get; private set; }
ICollectionChangeSet<TEntity, TProperty>? IChangesFrame.Get<TEntity, TProperty>(ICollection<TEntity, TProperty> collection)
{
if (Persons.Id == collection.Id) return Persons as ICollectionChangeSet<TEntity, TProperty>;
throw new System.InvalidOperationException("Unable to find collection's changes set");
}
IRelationChangeSet<TParent, TChild>? IChangesFrame.Get<TParent, TChild>(IRelation<TParent, TChild> relation)
{
throw new System.InvalidOperationException($"Unable to find relation's change set");
}
IChangesFrame IChangesFrame.Invert()
{
return new PersonShardChangesFrame()
{
Persons = Persons.Invert(),
};
}
public void Apply(IModel model)
{
var modelShard = model.Shard<IMutablePersonShardModelShard>();
Persons.Apply(modelShard.Persons);
}
public bool HasChanges()
{
return Persons.HasChanges();
}
public IChangesFrame Merge(IChangesFrame frame)
{
var typedFrame = (PersonShardChangesFrame)frame;
return new PersonShardChangesFrame()
{
Persons = Persons.Merge(typedFrame.Persons),
};
}
public void Save(IRepository repository)
{
repository.Save(PersonShardModelShardInfo.PersonsInfo, Persons);
}
}
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("C# Source Generator", "1.0.0.0")]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute()]
internal sealed class MutablePersonShardModelShard : IMutablePersonShardModelShard, IMutableState<IPersonShardModelShard>, ICanBeLoaded
{
public IMutableCollection<Person, PersonProperties> Persons { get; init; } = null!;
public IPersonShardModelShard AsReadOnly()
{
return new PersonShardModelShard(this);
}
public void Load(IRepository repository)
{
repository.Load(PersonShardModelShardInfo.PersonsInfo, Persons);
}
}
}
namespace Json2Code.Person.Entities
{
using CoreCraft.Core;
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("C# Source Generator", "1.0.0.0")]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute()]
public sealed record Person(global::System.Guid Id) : Entity(Id)
{
internal Person() : this(global::System.Guid.NewGuid())
{
}
}
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("C# Source Generator", "1.0.0.0")]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute()]
public sealed partial record PersonProperties : Properties
{
public PersonProperties()
{
FirstName = "Andrei";
LastName = "Ignat";
}
public string FirstName { get; init; }
public string LastName { get; init; }
#if NET5_0_OR_GREATER
public override PersonProperties ReadFrom(IPropertiesBag bag)
#else
public override Properties ReadFrom(IPropertiesBag bag)
#endif
{
return new PersonProperties()
{
FirstName = bag.Read<string>("FirstName"),
LastName = bag.Read<string>("LastName"),
};
}
public override void WriteTo(IPropertiesBag bag)
{
bag.Write("FirstName", FirstName);
bag.Write("LastName", LastName);
}
}
}
Usefull
Download Example (.NET C# )
Share corecraft
https://ignatandrei.github.io/RSCG_Examples/v2/docs/corecraft