Správičky 2 822 Blogy 948 Fórum 18 759

DDD Validacia / Autorizacia

photo
Liero
25.10.2018 16:54:25
Body: 9765
Najaktívnejší č.: 6

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. 37810 b. photo vlko
2. 21520 b. photo T
3. 15965 b. photo spigi
4. 15450 b. photo Anonymous
5. 11120 b. photo dudok
6. 9765 b. photo Liero
7. 6920 b. photo siro
8. 6245 b. photo slavof
9. 5395 b. photo duracellko
10. 4685 b. photo xxxmatko