Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
"Persistence": { /* ... */ },
"Projections": {
"Cassandra": {
"ConnectionString": "Contact Points=127.0.0.1;Port=9042;Default Keyspace=taskmanager_projections"
}
}dotnet add package Cronus.Projection.Cassandra
[DataContract(Name = "c94513d1-e5ee-4aae-8c0f-6e85b63a4e03")]
public class TaskProjection : ProjectionDefinition<TaskProjectionData, TaskId>,
IEventHandler<TaskCreated>
{
public TaskProjection()
{
//Id.NID - here we are subscribing by tenant
//in our case the tenant is: "tenant"
//so we well get all events
Subscribe<TaskCreated>(x => new TaskId(x.Id.NID));
}
public Task HandleAsync(TaskCreated @event)
{
Data task = new Data();
task.Id = @event.Id;
task.UserId = @event.UserId;
task.Name = @event.Name;
task.Timestamp = @event.Timestamp;
State.Tasks.Add(task);
return Task.CompletedTask;
}
public IEnumerable<Data> GetTaskByName(string name)
{
return State.Tasks.Where(x => x.Name.Equals(name));
}
}[DataContract(Name = "c135893e-b9e3-453a-b0e0-53545094ec5d")]
public class TaskProjectionData
{
public TaskProjectionData()
{
Tasks = new List<Data>();
}
[DataMember(Order = 1)]
public List<Data> Tasks { get; set; }
[DataContract(Name = "317b3cbb-593a-4ffc-8284-d5f5c599d8ae")]
public class Data
{
[DataMember(Order = 1)]
public TaskId Id { get; set; }
[DataMember(Order = 2)]
public UserId UserId { get; set; }
[DataMember(Order = 3)]
public string Name { get; set; }
[DataMember(Order = 4)]
public DateTimeOffset CreatedAt { get; set; }
[DataMember(Order = 5)]
public DateTimeOffset Timestamp { get; set; }
}
}[ApiController]
[Route("[controller]/[action]")]
public class TaskController : ControllerBase
{
private readonly IPublisher<CreateTask> _publisher;
private readonly IProjectionReader _projectionReader;
public TaskController(IPublisher<CreateTask> publisher, IProjectionReader reader)
{
_publisher = publisher;
_projectionReader = reader;
}
//.... create task code ..//
[HttpGet]
public async Task<IActionResult> GetTasksByName(string name)
{
ReadResult<TaskProjection> readResult = await _projectionReader.GetAsync<TaskProjection>(new TaskId("tenant"));
if (readResult.IsSuccess == false)
return NotFound();
var TasksByName = readResult.Data.GetTaskByName(name);
return Ok(TasksByName);
////Get all tasks
//return Ok(readResult.Data.State.Tasks.Select(x => new TaskData
//{
// CreatedAt = x.CreatedAt,
// Id = x.Id,
// Name = x.Name,
// Timestamp = x.Timestamp,
// UserId = x.UserId
//}));
}protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
logger.LogInformation("Starting service...");
cronusHost.Start();
// Dashboard configuration
cronusDashboard = CronusApi.GetHost();
cronusApi.Provider = cronusDashboard.Services;
await cronusDashboard.StartAsync().ConfigureAwait(false);
logger.LogInformation("Service started!");
}

[DataContract(Name = "857d960c-4b91-49cc-98fd-fa543906c52d")]
public class CreateTask : ICommand
{
public CreateTask() { }
public CreateTask(TaskId id, UserId userId, string name, DateTimeOffset timestamp
{
if (id is null) throw new ArgumentNullException(nameof(id));
if (userId is null) throw new ArgumentNullException(nameof(userId));
if (name is null) throw new ArgumentNullException(nameof(name));
if (timestamp == default) throw new ArgumentNullException(nameof(timestamp));
Id = id;
UserId = userId;
Name = name;
Timestamp = timestamp;
}
[DataMember(Order = 1)]
public TaskId Id { get; private set; }
[DataMember(Order = 2)]
public UserId UserId { get; private set; }
[DataMember(Order = 3)]
public string Name { get; private set; }
[DataMember(Order = 4)]
public DateTimeOffset Timestamp { get; private set; }
public override string ToString()
{
return $"Create a task with id '{Id}' and name '{Name}' for user [{UserId}].";
}
}CQRS
[DataContract(Name = "728fc4e7-628b-4962-bd68-97c98aa05694")]
public class TaskCreated : IEvent
{
TaskCreated() { }
public TaskCreated(TaskId id, UserId userId, string name, DateTimeOffset timestamp)
{
Id = id;
UserId = userId;
Name = name;
CreatedAt = DateTimeOffset.UtcNow;
Timestamp = timestamp;
}
[DataMember(Order = 1)]
public TaskId Id { get; private set; }
[DataMember(Order = 2)]
public UserId UserId { get; private set; }
[DataMember(Order = 3)]
public string Name { get; private set; }
[DataMember(Order = 4)]
public DateTimeOffset CreatedAt { get; private set; }
[DataMember(Order = 5)]
public DateTimeOffset Timestamp { get; private set; }
public override string ToString()
{
return $"Task with id '{Id}' and name '{Name}' for user [{UserId}] at {CreatedAt} has been created.";
}
}public class TaskAggregate : AggregateRoot<TaskState>
{
public TaskAggregate() { }
public void CreateTask(TaskId id, UserId userId, string name, DateTimeOffset deadline)
{
IEvent @event = new TaskCreated(id, userId, name, deadline);
Apply(@event);
}
}public class TaskState : AggregateRootState<TaskAggregate, TaskId>
{
public override TaskId Id { get; set; }
public UserId UserId { get; set; }
public string Name { get; set; }
public DateTimeOffset CreatedAt { get; set; }
public DateTimeOffset Deadline { get; set; }
public void When(TaskCreated @event)
{
Id = @event.Id;
UserId = @event.UserId;
Name = @event.Name;
CreatedAt = @event.CreatedAt;
Deadline = @event.Timestamp;
}
}[DataContract(Name = "ef669879-5d35-4cb7-baea-39a7c46c9e13")]
public class TaskService : ApplicationService<TaskAggregate>,
ICommandHandler<CreateTask>
{
public TaskService(IAggregateRepository repository) : base(repository) { }
public async Task HandleAsync(CreateTask command)
{
ReadResult<TaskAggregate> taskResult = await repository.LoadAsync<TaskAggregate>(command.Id).ConfigureAwait(false);
if (taskResult.NotFound)
{
var task = new TaskAggregate();
task.CreateTask(command.Id, command.UserId, command.Name, DateTimeOffset.UtcNow);
await repository.SaveAsync(task).ConfigureAwait(false);
}
}
}[ApiController]
[Route("[controller]/[action]")]
public class TaskController : ControllerBase
{
private readonly IPublisher<ICommand> _publisher;
public TaskController(IPublisher<ICommand> publisher)
{
_publisher = publisher;
}
[HttpPost]
public IActionResult CreateTask(CreateTaskRequest request)
{
string id = Guid.NewGuid().ToString();
string Userid = Guid.NewGuid().ToString();
TaskId taskId = new TaskId(id);
UserId userId = new UserId(Userid);
var expireDate = DateTimeOffset.UtcNow;
expireDate.AddDays(request.DaysActive);
CreateTask command = new CreateTask(taskId, userId, request.Name, expireDate);
if (_publisher.Publish(command) == false)
{
return Problem($"Unable to publish command. {command.Id}: {command.Name}");
};
return Ok(id);
}
}public class CreateTaskRequest
{
[Required]
public string Name { get; set; }
[Required]
public int DaysActive { get; set; }
}> dotnet new console --name TaskManager.Service dotnet new webapi --name TaskManager.Apicd TaskManager.Api
dotnet add package Cronus
cd ../TaskManager.Service
dotnet add package Cronus
dotnet add package Cronus.Transport.RabbitMQ
dotnet add package Cronus.Persistence.Cassandra
dotnet add package Cronus.Serialization.NewtonsoftJson
dotnet add package Microsoft.Extensions.Hosting
{
"Cronus": {
"BoundedContext": "taskmanager",
"Tenants": [ "tenant" ],
"Transport": {
"RabbitMQ": {
"Server": "127.0.0.1",
"VHost": "taskmanager"
},
"PublicRabbitMQ": [
{
"Server": "127.0.0.1",
"VHost": "unicom-public",
"FederatedExchange": {
"UpstreamUri": "guest:guest@localhost:5672",
"VHost": "unicom-public",
"UseSsl": false,
"MaxHops": 1
}
}
]
},
"Persistence": {
"Cassandra": {
"ConnectionString": "Contact Points=127.0.0.1;Port=9042;Default Keyspace=taskmanager_es"
}
},
"Projections": {
"Cassandra": {
"ConnectionString": "Contact Points=127.0.0.1;Port=9042;Default Keyspace=taskmanager_projections"
}
},
"Cluster": {
"Consul": {
"Address": "127.0.0.1"
}
},
"AtomicAction": {
"Redis": {
"ConnectionString": "127.0.0.1:6379"
}
}
}
}using Cronus11Service;
using Elders.Cronus;
IHost host = Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<Worker>();
services.AddCronus(hostContext.Configuration);
})
.UseDefaultServiceProvider((context, options) =>
{
options.ValidateScopes = context.HostingEnvironment.IsDevelopment();
options.ValidateScopes = false;
options.ValidateOnBuild = false;
})
.Build();
host.Run();builder.Services.AddCronus(builder.Configuration);
builder.Host.UseDefaultServiceProvider((context, options) =>
{
options.ValidateScopes = context.HostingEnvironment.IsDevelopment();
options.ValidateScopes = false;
options.ValidateOnBuild = false;
}
);
....
app.UseCronusAspNetCore();
public class Concert : AggregateRoot<ConcertState>
{
Concert() {} // keep the private parameterless constructor
public Concert(string name, Venue venue, DateTimeOffset startTime, TimeSpan duration)
{
// business logic for creating a concert
Apply(new ConcertAnnounced(...));
}
public void RegisterPerformer(Performer performer)
{
// business logic for registering a performer
Apply(new PerformerRegistered(...));
}
// ...
}public class ConcertState : AggregateRootState<Concert, ConcertId>
{
public ConcertState()
{
Performers = new List<Performer>();
}
public override ConcertId Id { get; set; }
public string Name { get; private set; }
public Venue Venue { get; private set; }
public DateTimeOffset StartTime { get; private set; }
public TimeSpan Duration { get; private set; }
public List<Performer> Performers { get; private set; }
public void When(ConcertAnnounced @event)
{
// change the state here ...
}
public void When(PerformerRegistered @event)
{
// change the state here ...
}
}[DataContract(Name = "e96d90d0-4943-43f4-8a84-cd90b1217d06")]
public class ConcertId : AggregateRootId
{
const string RootName = "concert";
public ConcertId(AggregateUrn urn) : base(RootName, urn) { }
public ConcertId(string idBase, string tenant) : base(idBase, RootName, tenant) { }
protected ConcertId() { }
}[DataContract(Name = "e96d90d0-4943-43f4-8a84-cd90b1217d06")]
public class ConcertId : AggregateRootId<ConcertId>
{
const string RootName = "concert";
ConcertId() { }
public ConcertId(string id, string tenant) : base(id, RootName, tenant) { }
protected override ConcertId Construct(string id, string tenant)
{
return new ConcertId(id, tenant);
}
}public class Wallet : Entity<UserAggregate, WalletState>
{
public Wallet(UserAggregate root, WalletId entityId, string name, decimal amount) : base(root, entityId)
{
state.EntityId = entityId;
state.Name = name;
state.Amount = amount;
}
public void AddMoney(decimal value, UserId userId)
{
if (value > 0)
{
IEvent @event = new AddMoney(state.EntityId, userId, value, DateTimeOffset.UtcNow);
Apply(@event);
}
}
}!=[DataContract(Name = "1b6187f0-88c7-46d5-a22d-b39301765412")]
public class Performer: ValueObject<Performer>
{
Performer() {}
public Performer(string name, string coverImage)
{
// null check
Name = name;
CoverImage = coverImage;
}
[DataMember(Order = 1)]
public string Name { get; private set; }
[DataMember(Order = 2)]
public string CoverImage { get; private set; }
}public class WalletState : EntityState<WalletId>
{
public override WalletId EntityId { get; set; }
public string Name { get; set; }
public decimal Amount { get; set; }
}[DataContract(Name = "1d23c591-219f-491e-bfb1-a775fe2751b6")]
public class WalletId : EntityId<UserId>
{
protected override ReadOnlySpan<char> EntityName => "wallet";
WalletId() { }
public WalletId(string id, UserId idBase) : base(id.AsSpan(), idBase) { }
}[DataContract(Name = "728fc4e7-628b-4962-bd68-97c98aa05694")]
public class TaskCreated : IEvent
{
TaskCreated() { }
public TaskCreated(TaskId id, UserId userId, string name, DateTimeOffset timestamp)
{
Id = id;
UserId = userId;
Name = name;
CreatedAt = DateTimeOffset.UtcNow;
Timestamp = timestamp;
}
[DataMember(Order = 1)]
public TaskId Id { get; private set; }
[DataMember(Order = 2)]
public UserId UserId { get; private set; }
[DataMember(Order = 3)]
public string Name { get; private set; }
[DataMember(Order = 4)]
public DateTimeOffset CreatedAt { get; private set; }
[DataMember(Order = 5)]
public DateTimeOffset Timestamp { get; private set; }
public override string ToString()
{
return $"Task with id '{Id}' and name '{Name}' for user [{UserId}] at {CreatedAt} has been created.";
}
}public class ConcertAppService : AggregateRootApplicationService<Concert>,
ICommandHandler<AnnounceConcert>,
ICommandHandler<RegisterPerformer>
{
...
public void Handle(AnnounceConcert command)
{
if (Repository.TryLoad<Concert>(command.Id, out _))
return;
var concert = new Concert(...);
Repository.Save(concert);
}
public void Handle(RegisterPerformer command)
{
Update(command.Id, x => x.RegisterPerformer(...));
}
...
}[DataContract(Name = "d4eb8803-2cc7-48dd-9ca1-4512b8d9b88f")]
public class TaskSaga : Saga,
IEventHandler<UserCreated>,
ISagaTimeoutHandler<Message>
{
public TaskSaga(IPublisher<ICommand> commandPublisher, IPublisher<IScheduledMessage> timeoutRequestPublisher) : base(commandPublisher, timeoutRequestPublisher)
{
}
public Task HandleAsync(UserCreated @event)
{
var message = new Message();
message.Info = @event.Name + "was created yesterday.";
message.PublishAt = DateTimeOffset.UtcNow.AddDays(1).DateTime;
message.Timestamp = DateTimeOffset.UtcNow;
RequestTimeout<Message>(message);
return Task.CompletedTask;
}
public Task HandleAsync(Message sagaTimeout)
{
Console.WriteLine(sagaTimeout.Info);
return Task.CompletedTask;
}
}
[DataContract(Name = "543e8e28-0dcb-4d41-98de-f701e403dbb2")]
public class Message : IScheduledMessage
{
public string Info { get; set; }
public DateTime PublishAt { get; set; }
public DateTimeOffset Timestamp { get; set; }
}[DataContract(Name = "c94513d1-e5ee-4aae-8c0f-6e85b63a4e03")]
public class TaskProjection : ProjectionDefinition<TaskProjectionData, TaskId>,
IEventHandler<TaskCreated>
{
public TaskProjection()
{
Subscribe<TaskCreated>(x => new TaskId(x.Id.NID));
}
public Task HandleAsync(TaskCreated @event)
{
Data task = new Data();
task.Id = @event.Id;
task.UserId = @event.UserId;
task.Name = @event.Name;
task.Timestamp = @event.Timestamp;
State.Tasks.Add(task);
return Task.CompletedTask;
}
public IEnumerable<Data> GetTaskByName(string name)
{
return State.Tasks.Where(x => x.Name.Equals(name));
}
}[DataContract(Name = "c135893e-b9e3-453a-b0e0-53545094ec5d")]
public class TaskProjectionData
{
public TaskProjectionData()
{
Tasks = new List<Data>();
}
[DataMember(Order = 1)]
public List<Data> Tasks { get; set; }
[DataContract(Name = "317b3cbb-593a-4ffc-8284-d5f5c599d8ae")]
public class Data
{
[DataMember(Order = 1)]
public TaskId Id { get; set; }
[DataMember(Order = 2)]
public UserId UserId { get; set; }
[DataMember(Order = 3)]
public string Name { get; set; }
[DataMember(Order = 4)]
public DateTimeOffset CreatedAt { get; set; }
[DataMember(Order = 5)]
public DateTimeOffset Timestamp { get; set; }
}
}// TODO: give a relevant example
[DataContract(Name = "af157a4d-7608-4c9d-8e42-63bd483a8ad4")]
public class ExampleEfProjection : IProjection,
IEventHandler<ExampleCreated>
{
public DbContext Context { get; set; }
public void Handle(ExampleCreated @event)
{
var exampleDto = new ExampleDto(@event.Id, @event.Name);
Context.Examples.Add(exampleDto);
Context.SaveChanges();
}
}// TODO: give a relevant example
[DataContract(Name = "bae8bd10-9903-4960-95c4-b4fa4688a860")]
public class ExampleByIdProjection : ProjectionDefinition<ExampleByIdProjectionState, ExampleId>,
IEventHandler<ExampleCreated>,
IAmNotSnapshotable
{
// ...
}public class GetExampleController : ControllerBase
{
private IProjectionReader projectionReader;
public GetExampleController(IProjectionReader projectionReader)
{
this.projectionReader = projectionReader;
}
public async Task<IActionResult> GetExample(GetExampleRequest request)
{
var id = ExampleId.New(request.Tenant, request.Id);
var result = await projectionReader.GetAsync<ExampleByIdProjection>(id);
if (result.IsSuccess)
return Ok(new GetExampleResponse(result.Data.State));
else
return BadRequest(result.Error);
}
public class GetExampleResponse
{
// ...
}
}[DataContract(Name = "a44e9a38-ab13-4f86-844a-86fefa925b53")]
public class AlertPort : IPort,
IEventHandler<UserCreated>
{
public Task HandleAsync(UserCreated @event)
{
//Implement your custom logic here
return Task.CompletedTask;
}
}[DataContract(Name = "857d960c-4b91-49cc-98fd-fa543906c52d")]
public class CreateTask : ICommand
{
public CreateTask() { }
public CreateTask(TaskId id, UserId userId, string name, DateTimeOffset timestamp)
{
if (id is null) throw new ArgumentNullException(nameof(id));
if (userId is null) throw new ArgumentNullException(nameof(userId));
if (name is null) throw new ArgumentNullException(nameof(name));
if (timestamp == default) throw new ArgumentNullException(nameof(timestamp));
Id = id;
UserId = userId;
Name = name;
Timestamp = timestamp;
}
[DataMember(Order = 1)]
public TaskId Id { get; private set; }
[DataMember(Order = 2)]
public UserId UserId { get; private set; }
[DataMember(Order = 3)]
public string Name { get; private set; }
[DataMember(Order = 4)]
public DateTimeOffset Timestamp { get; private set; }
public override string ToString()
{
return $"Create a task with id '{Id}' and name '{Name}' for user [{UserId}].";
}
}[ApiController]
[Route("[controller]/[action]")]
public class TaskController : ControllerBase
{
private readonly IPublisher<ICommand> _publisher;
public TaskController(IPublisher<ICommand> publisher)
{
_publisher = publisher;
}
[HttpPost]
public IActionResult CreateTask(CreateTaskRequest request)
{
string id = Guid.NewGuid().ToString();
string Userid = Guid.NewGuid().ToString();
TaskId taskId = new TaskId(id);
UserId userId = new UserId(Userid);
var expireDate = DateTimeOffset.UtcNow;
expireDate.AddDays(request.DaysActive);
CreateTask command = new CreateTask(taskId, userId, request.Name, expireDate);
if (_publisher.Publish(command) == false)
{
return Problem($"Unable to publish command. {command.Id}: {command.Name}");
};
return Ok(id);
}
}Name of the DataContractAttribute when already deployed to productionCronus:Projections:Cassandra:ConnectionStringCronus:Projections:Cassandra:ReplicationStrategyCronus:Projections:Cassandra:ReplicationFactorCronus:Projections:Cassandra:DatacentersCronus:Projections:Cassandra:TableRetention:DeleteOldProjectionTables Cronus:Projections:Cassandra:TableRetention:NumberOfOldProjectionTablesToRetain Cronus:Transport:RabbiMQ:ConsumerWorkersCount >> integer | Required: Yes | Default: 5Cronus:Transport:RabbiMQ:Server >> string | Required: Yes | Default: 127.0.0.1Cronus:Transport:RabbiMQ:Port >> integer | Required: Yes | Default: 5672Cronus:Transport:RabbiMQ:VHost >> string | Required: Yes | Default: /Cronus:Transport:RabbiMQ:Username >> string | Required: Yes | Default: guestCronus:Transport:RabbiMQ:Password >> string | Required: Yes | Default: guestCronus:Transport:RabbiMQ:AdminPort >> integer | Required: Yes | Default: 5672Cronus:AtomicAction:Redis:ConnectionString >> string | Required: YesCronus:AtomicAction:Redis:LockTtl >> TimeSpan | Required: No | Default: 00:00:01.000Cronus:AtomicAction:Redis:ShorTtl >> TimeSpan | Required: No | Default: 00:00:01.000Cronus:AtomicAction:Redis:LongTtl >> TimeSpan | Required: No | Default: 00:00:05.000Cronus:AtomicAction:Redis:LockRetryCount >> int | Required: No | Default: 3Cronus:AtomicAction:Redis:LockRetryDelay >> TimeSpan | Required: No | Default: 00:00:00.100Cronus:AtomicAction:Redis:ClockDriveFactor >> double | Required: No | Default: 0.01{
"Cronus": {
"Api": {
"Kestrel": {
"Endpoints": {
"Https": {
"Url": "https://*:7477",
"Certificate": {
"Subject": "*.example.com",
"Store": "My",
"Location": "CurrentUser",
"AllowInvalid": "true"
}
}
}
}
}
}
}{
"Cronus": {
"Api": {
"JwtAuthentication": {
"Authority": "https://example.com",
"Audience": "https://example.com/resources"
}
}
}
}TimeZoneInfo [DataContract(Namespace = BC.ContextName, Name = "dce741fb-8671-42b8-af59-d30aaae27bad")]
public struct Cycle
{
[DataMember(Order = 1)]
private DateTimeOffset _start;
[DataMember(Order = 2)]
private DateTimeOffset _end;
[DataMember(Order = 3)]
private TimeSpan _duration;
[DataMember(Order = 4)]
private readonly TimeZoneInfo _timezone;
}{
...
_timezone = TZConvert.GetTimeZoneInfo("Central Standard Time");
...
} [DataContract(Namespace = BC.ContextName, Name = "dce741fb-8671-42b8-af59-d30aaae27bad")]
public struct Cycle
{
[DataMember(Order = 1)]
private DateTimeOffset _start;
[DataMember(Order = 2)]
private DateTimeOffset _end;
[DataMember(Order = 3)]
private TimeSpan _duration;
[DataMember(Order = 4)]
private readonly string _timezoneId;
}