Správičky 2 826 Blogy 948 Fórum 56 517

DDD Validacia / Autorizacia

photo
Liero
25.10.2018 16:54:25
Body: 9780
Najaktívnejší č.: 10

DDD Validacia / Autorizacia

Pri refactoringu Entity Frameworku pre .net core sa vraj zamerali hlavne na jeho pouzitie ako repository pre DDD model a rozhodol som sa to vyskusat. Hlavne teda moznost pisat entity/agregaty tak, aby neboli modifikovatelne zvonka inak ako business metodami a zatial som nenarazil na problem.
 
Keby niekoho zaujimalo, v tomto priklade su private fieldy, public readonly kolekcie a tiez skryte mapovanie IEnumerable<string> na nieco, co sa da ulozit v databaze:
 
 
public class ForumThread
{
    private List<Tag> _tags = new List<Tag>();

    private ForumThread() { } //ef ctor

    public ForumThread(string title, string content, string[] tags, string author)
    {
        Id = Guid.NewGuid();
        Title = title;
        Content = content;
        SetTags(tags);
        CreatedBy = author;
        CreatedDate = DateTime.UtcNow;
        ModifiedDate = CreatedDate;
    }

    public Guid Id { get; private set; }
    public string Title { get; private set; }
    public string Content { get; private set; }
    public IEnumerable<string> Tags => _tags.OrderBy(t => t.OrderNumber).Select(t => t.Name).ToArray();

    public string CreatedBy { get; private set; }
    public DateTime CreatedDate { get; private set; }
    public string ModifiedBy { get; set; }
    public DateTime ModifiedDate { get; set; }

    public void Edit(string title, string content, string[] tags, string reason, string username)
    {
        Title = title;
        Content = content;
        SetTags(tags);
        ModifiedDate = DateTime.UtcNow;
        ModifiedBy = username;
    }

    private void SetTags(string[] tags)
    {
        //remove tags
        _tags.RemoveAll(t =>
            !tags.Any(tagName => t.Name.EqualsIgnoreCase(tagName)));

        //add tags
        for (int i = 0; i < tags.Length; i++)
        {
            string tagName = tags[i];
            Tag tag = _tags.FindByName(tagName);
            if (tag != null) tag.OrderNumber = i;// tag position could have changed
            else
            {
                _tags.Add(new Tag(Id, tagName, i));
            }
        }
    }
}

public class Tag
{
    private Tag() { }
    internal Tag(Guid contentId, string name, int orderNumber)
    {
        ContentId = contentId;
        Name = name.ToLower();
        OrderNumber = orderNumber;
    }

    public Guid ContentId { get; private set; }
    public string Name { get; private set; }
    public int OrderNumber { get; internal set; }
}


public class MyDomainContext : DbContext
{
    public MyDomainContext(DbContextOptions options) : base(options)
    {
    }

    public DbSet<ForumThread> ForumThreads { get; private set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<ForumThread>(thread =>
        {
            var metadata = thread.HasMany<Tag>("_tags")
                .WithOne()
                .HasForeignKey(nameof(Tag.ContentId))
                .Metadata;


            thread.Metadata.SetNavigationAccessMode(PropertyAccessMode.Field);
        });
        modelBuilder.Entity<Tag>(tag =>
        {
            tag.Metadata.SetNavigationAccessMode(PropertyAccessMode.Field);
            tag.HasKey(t => new { t.ContentId, t.Name });
        });
    }
}

Moja otazka znie:
Aky je podla vas najvhodnesi sposob implementacie:
1. Autorizacie
2. Business validacie (nie overovanie null parametrov a podobne)
Samozrejme v kontexte projektu (vyvojari.sk) a tiez framworku (asp.net core).
Co sa tyka specifik frameworku, len pripominam zabudovany AutorizationService, ktory pouziva
policies, co je vlastne zoznam IAuthorizationRequirement a AuthorizationHandler<IAuthorizationRequirement>.
Policy potom moze vyzerat takto:
    public static class Policies
    {
        public const string EditPolicy = "EditPolicy";
        public const string DeletePolicy = "DeletePolicy";

        public static void Configure(AuthorizationOptions authorization)
        {
            authorization.AddPolicy(EditPolicy, builder => builder
                .AddRequirements(
		    new IsContentOwner()
                    new NotOlderThan(Timespan.FromDay(1))));

            authorization.AddPolicy(DeletePolicy, builder => builder
                .AddRequirements(new IsContentOwner()));
        }
    }

Maju byt tieto typu validacie sucastou domenoveho modelu? Ak ano, ma byt sucastou agregatu, alebo externy service?
Pokial sa bavime o busimess validacie, napriklad ForumThread.Edit:
 
public void Edit(string title, string content, string[] tags, string reason, string username)
{
    if (this.IsDeleted) throw new BusinessValidationException("Cannot edit deleted thread");
}
 
alebo
public ICommandStatus Edit(string title, string content, string[] tags, string reason, string username)
{
    if (this.IsDeleted) return CommandStatus.Fail("Cannot edit deleted thread");
    return CommandStatus.Ok;
}


alebo:
public ValidationResult CanEdit(ForumThreadEditCommand command) { ... }

public void Edit(ForumThreadEditCommand command)
{
    /* mozno aj toto:
    * if (CanEdit(command).Failed) throw new BusinessValidationException("Cannot edit deleted thread");
    */
    return CommandStatus.Ok;
}

alebo

public ICommandStatus Edit(ForumThreadEditCommand command)
{
    var status = CanEdit(command);
    if (!status.Failed) return status;
    return CommandStatus.Ok;
}
atd.
Co sa tyka autorizacie, tak moznosti su podobne, ale znamenalo by to, ze do domeoveho modelu by sa musel zaviest IPrincipal.
 
A samozrejme, ked sa zavedie autorizacia do agregatov, tak to znamena, ze plati vzdy a vsade, nielen v kontexte danej aplikacie.

[Reakcia]



Najaktívnejší užívatelia
1. 48800 b. photo xmhlxzybzzyq998
2. 48110 b. photo bfdgfdhfdhfd06
3. 42045 b. photo pmpindia123
4. 37835 b. photo vlko
5. 21520 b. photo T
6. 20600 b. photo quickydocs
7. 15965 b. photo spigi
8. 15450 b. photo Anonymous
9. 11120 b. photo dudok
10. 9780 b. photo Liero