Straight.Core 1.0.1

Package Description

Install-Package Straight.Core -Version 1.0.1
dotnet add package Straight.Core --version 1.0.1
<PackageReference Include="Straight.Core" Version="1.0.1" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Straight.Core --version 1.0.1
The NuGet Team does not provide support for this client. Please contact its maintainers for support.

Straight.Core

A set of simple implementaion of architectural patterns like CQRS, Event Sourcing, Domain-Driven Design.
Command and Query Responsibility Segregation (CQRS) is a pattern that segregates the operations that read data (Queries) from the operations that update data (Commands) by using separate interfaces.

With this Framework, We are focus on the "Domain", because resolve an Business issue is the only one way to improve your solution. All reasons, which bring to focus on another thing, are lost time.

Two principal has been used to simplify the using of this framework, the programation by "Convention" and the reflexion. The "convention" allow to protected all methods inside the Domain when you handle your command in your model or when the event has been apply. The framework load by reflexion all methods with name of "Handle" which ICommand object and all methods with name of "Apply" which contains IEvent object. All handle methods and apply methods can be private, these will be call by the base of AggregateBase.

Sample of Domain

This model is an Account, it is Write model for Account, his name is AggregatorAccount. Aggregator, because he aggregates all events which composed what it is.


public class AggregatorAccount : AggregatorBase<IDomainEvent>
{
    
    private IEnumerable Handle(CreateAccountCommand command)
    {
        var creator = new User(command.CreatorLastName, command.CreatorFirstName, command.CreatorUsername);
        yield return new AccountCreated(command.AccountKey, command.Login, command.Password, creator);
    }

    private void Apply(AccountCreated @event)
    {
        _creator = @event.Creator;
        _connectionInformation = @event.ConnectionInfo;
    }

    private IEnumerable<IDomainEvent> Handle(AddVisitCommand command)
    {
        command.CheckIfArgumentIsNull("command");
        command.House.CheckIfArgumentIsNull("Address");
        AddressHelper.CheckMandatory(
            command.House.Address.Street,
            command.House.Address.City,
            command.House.Address.PostalCode);
        var estateOfficier = new User(command.EstateOfficierLastName,
            command.EstateOfficierFirstName,
            command.EstateOfficierUsername);
        if (IsInCurrentMeet(command.MeetDate))
        {
            throw new DateAlreadyExistException(command.MeetDate);
        }

        yield return new VisitAdded(command.House, estateOfficier, command.MeetDate);
    }

    private void Apply(VisitAdded @event)
    {
        _lastModifier = @event.EstateOfficier;
        _allMeetDates.Add(@event.MeetDate);
    }
}

His read model, this model will be stimulate when the bus raises all events generated by an command.

public class Account : ReadModelBase<IDomainEvent>, IAccount
{
    public User Creator { get; private set; }
    public User LastModifier { get; private set; }
    public int VisitCounter { get;private set; }
    public IHouse LastHouseVisited { get; private set; }
    public IOfficer LastOfficer { get; private set; }

    private void Apply(AccountCreated @event)
    {
        Creator = @event.Creator;

    }
    
    private void Apply(VisitAdded @event)
    {
        LastModifier = @event.EstateOfficier;
        VisitCounter++;
        LastHouseVisited = @event.House;
        LastOfficer = @event.Officier;
    }

}

Command Handler ?

public class AccountCommandHandler : ICommandHandler<CreateAccountCommandDto>
{
    private readonly IDomainEventStore<IDomainEvent> _repository;
    private readonly IModelConverter _converter;

    /// This constructor is built by the IoC and all dependecies has been injected by the IoC too
    public AccountCommandHandler(IDomainEventStore<IDomainEvent> repository, IModelConverter converter)
    {
        _repository = repository;
        _converter = converter;
    }

    public void Handle(CreateAccountCommandDto command)
    {
        command.CheckIfArgumentIsNull(nameof(command));
        command.Customers.CheckIfArgumentIsNullOrEmpty(nameof(command.Customers));
        command.Creator.CheckIfArgumentIsNull(nameof(command.Creator));        
        // it is possible to replace that by _repository.GetById<AggregatorAccount>(command.Id); and inside your repository you create your account.
        // I prefere keep the controle when I create a new instance and when I load an instance, and the second argument is "Segragation of responsability", 
        // when you "Get" an instance, you do not need to create a new instance.   
        var account = new AggregatorAccount();
        account.Update(new CreateEmployeAccountCommand
        {
            CreatorFirstName = command.Creator.FirstName,
            CreatorLastName = command.Creator.LastName,
            CreatorUsername = command.Creator.Username,
            Customers = command.Customers.Select(c => _converter.ToModel<Customer>(c)).ToList()
        });
        _repository.Add(account);
    }
}

Your repository can be File, Database, dataobj serialization etc.

Bus or not Bus ?

Sure ... BUS !!! :)

Provide with this framework it is "InMemoryBus"
If you implement IBus, you can inject RabbitMQ for example.

How I can call my Command Handler ?

    [Route("api/[controller]")]
    [ApiController]
    [ApiVersion("1.0")]
    public class AccountController : ControllerBase
    {
        private IBus bus;

        public AccountController(IBus bus, IReadModelRepository<User> userRepo)
        {
            this.bus = bus;
            this.userRepo = userRepo;
        }
        
        [HttpPost]
        public IActionResult Post([FromBody] CreateAccountCommandDto command)
        {
            if(!command.IsValid)
            {
                return BadRequest();
            }
            var creator = userRepo.Get(this.User.Identity.Id);
            if(creator == null)
            {
                return this.BadRequest("Not authorized");
            }
            bus.Publish(command);
            return Ok();
        }
    }
}

Straight.Core

A set of simple implementaion of architectural patterns like CQRS, Event Sourcing, Domain-Driven Design.
Command and Query Responsibility Segregation (CQRS) is a pattern that segregates the operations that read data (Queries) from the operations that update data (Commands) by using separate interfaces.

With this Framework, We are focus on the "Domain", because resolve an Business issue is the only one way to improve your solution. All reasons, which bring to focus on another thing, are lost time.

Two principal has been used to simplify the using of this framework, the programation by "Convention" and the reflexion. The "convention" allow to protected all methods inside the Domain when you handle your command in your model or when the event has been apply. The framework load by reflexion all methods with name of "Handle" which ICommand object and all methods with name of "Apply" which contains IEvent object. All handle methods and apply methods can be private, these will be call by the base of AggregateBase.

Sample of Domain

This model is an Account, it is Write model for Account, his name is AggregatorAccount. Aggregator, because he aggregates all events which composed what it is.


public class AggregatorAccount : AggregatorBase<IDomainEvent>
{
    
    private IEnumerable Handle(CreateAccountCommand command)
    {
        var creator = new User(command.CreatorLastName, command.CreatorFirstName, command.CreatorUsername);
        yield return new AccountCreated(command.AccountKey, command.Login, command.Password, creator);
    }

    private void Apply(AccountCreated @event)
    {
        _creator = @event.Creator;
        _connectionInformation = @event.ConnectionInfo;
    }

    private IEnumerable<IDomainEvent> Handle(AddVisitCommand command)
    {
        command.CheckIfArgumentIsNull("command");
        command.House.CheckIfArgumentIsNull("Address");
        AddressHelper.CheckMandatory(
            command.House.Address.Street,
            command.House.Address.City,
            command.House.Address.PostalCode);
        var estateOfficier = new User(command.EstateOfficierLastName,
            command.EstateOfficierFirstName,
            command.EstateOfficierUsername);
        if (IsInCurrentMeet(command.MeetDate))
        {
            throw new DateAlreadyExistException(command.MeetDate);
        }

        yield return new VisitAdded(command.House, estateOfficier, command.MeetDate);
    }

    private void Apply(VisitAdded @event)
    {
        _lastModifier = @event.EstateOfficier;
        _allMeetDates.Add(@event.MeetDate);
    }
}

His read model, this model will be stimulate when the bus raises all events generated by an command.

public class Account : ReadModelBase<IDomainEvent>, IAccount
{
    public User Creator { get; private set; }
    public User LastModifier { get; private set; }
    public int VisitCounter { get;private set; }
    public IHouse LastHouseVisited { get; private set; }
    public IOfficer LastOfficer { get; private set; }

    private void Apply(AccountCreated @event)
    {
        Creator = @event.Creator;

    }
    
    private void Apply(VisitAdded @event)
    {
        LastModifier = @event.EstateOfficier;
        VisitCounter++;
        LastHouseVisited = @event.House;
        LastOfficer = @event.Officier;
    }

}

Command Handler ?

public class AccountCommandHandler : ICommandHandler<CreateAccountCommandDto>
{
    private readonly IDomainEventStore<IDomainEvent> _repository;
    private readonly IModelConverter _converter;

    /// This constructor is built by the IoC and all dependecies has been injected by the IoC too
    public AccountCommandHandler(IDomainEventStore<IDomainEvent> repository, IModelConverter converter)
    {
        _repository = repository;
        _converter = converter;
    }

    public void Handle(CreateAccountCommandDto command)
    {
        command.CheckIfArgumentIsNull(nameof(command));
        command.Customers.CheckIfArgumentIsNullOrEmpty(nameof(command.Customers));
        command.Creator.CheckIfArgumentIsNull(nameof(command.Creator));        
        // it is possible to replace that by _repository.GetById<AggregatorAccount>(command.Id); and inside your repository you create your account.
        // I prefere keep the controle when I create a new instance and when I load an instance, and the second argument is "Segragation of responsability", 
        // when you "Get" an instance, you do not need to create a new instance.   
        var account = new AggregatorAccount();
        account.Update(new CreateEmployeAccountCommand
        {
            CreatorFirstName = command.Creator.FirstName,
            CreatorLastName = command.Creator.LastName,
            CreatorUsername = command.Creator.Username,
            Customers = command.Customers.Select(c => _converter.ToModel<Customer>(c)).ToList()
        });
        _repository.Add(account);
    }
}

Your repository can be File, Database, dataobj serialization etc.

Bus or not Bus ?

Sure ... BUS !!! :)

Provide with this framework it is "InMemoryBus"
If you implement IBus, you can inject RabbitMQ for example.

How I can call my Command Handler ?

    [Route("api/[controller]")]
    [ApiController]
    [ApiVersion("1.0")]
    public class AccountController : ControllerBase
    {
        private IBus bus;

        public AccountController(IBus bus, IReadModelRepository<User> userRepo)
        {
            this.bus = bus;
            this.userRepo = userRepo;
        }
        
        [HttpPost]
        public IActionResult Post([FromBody] CreateAccountCommandDto command)
        {
            if(!command.IsValid)
            {
                return BadRequest();
            }
            var creator = userRepo.Get(this.User.Identity.Id);
            if(creator == null)
            {
                return this.BadRequest("Not authorized");
            }
            bus.Publish(command);
            return Ok();
        }
    }
}

This package is not used by any popular GitHub repositories.

Version History

Version Downloads Last updated
1.0.1 98 8/4/2019
1.0.0 83 8/3/2019