Katmanlı mimaride doğrulama ve yetkilendirme


13

"Doğrulamanın katmanlı bir mimaride nereye ait olduğunu soran başka bir soru değil mi?" Evet, ama umarım bu konuya biraz farklı bir bakış getirecektir.

Doğrulamanın pek çok biçim aldığını, bağlam temelli olduğunu ve mimarinin her düzeyinde değiştiğini kesin olarak inanıyorum. Her katmanda ne tür bir doğrulama yapılması gerektiğini belirlemeye yardımcı olan post sonrası için temel budur. Ayrıca, sıkça ortaya çıkan bir soru, yetkilendirme kontrollerinin nereye ait olduğudur.

Örnek senaryo bir catering işletmesi için yapılan başvurudur. Gün boyunca periyodik olarak, bir sürücü kamyonu bir yerden bir yere götürürken birikmiş oldukları fazla parayı ofise teslim edebilir. Uygulama, kullanıcının sürücü kimliğini ve miktarını toplayarak 'nakit düşüşünü' kaydetmesini sağlar. İlgili katmanları gösteren bazı iskelet kodları:

public class CashDropApi  // This is in the Service Facade Layer
{
    [WebInvoke(Method = "POST")]
    public void AddCashDrop(NewCashDropContract contract)
    {
        // 1
        Service.AddCashDrop(contract.Amount, contract.DriverId);
    }
}

public class CashDropService  // This is the Application Service in the Domain Layer
{
    public void AddCashDrop(Decimal amount, Int32 driverId)
    {
        // 2
        CommandBus.Send(new AddCashDropCommand(amount, driverId));
    }
}

internal class AddCashDropCommand  // This is a command object in Domain Layer
{
    public AddCashDropCommand(Decimal amount, Int32 driverId)
    {
        // 3
        Amount = amount;
        DriverId = driverId;
    }

    public Decimal Amount { get; private set; }
    public Int32 DriverId { get; private set; }
}

internal class AddCashDropCommandHandler : IHandle<AddCashDropCommand>
{
    internal ICashDropFactory Factory { get; set; }       // Set by IoC container
    internal ICashDropRepository CashDrops { get; set; }  // Set by IoC container
    internal IEmployeeRepository Employees { get; set; }  // Set by IoC container

    public void Handle(AddCashDropCommand command)
    {
        // 4
        var driver = Employees.GetById(command.DriverId);
        // 5
        var authorizedBy = CurrentUser as Employee;
        // 6
        var cashDrop = Factory.CreateCashDrop(command.Amount, driver, authorizedBy);
        // 7
        CashDrops.Add(cashDrop);
    }
}

public class CashDropFactory
{
    public CashDrop CreateCashDrop(Decimal amount, Employee driver, Employee authorizedBy)
    {
        // 8
        return new CashDrop(amount, driver, authorizedBy, DateTime.Now);
    }
}

public class CashDrop  // The domain object (entity)
{
    public CashDrop(Decimal amount, Employee driver, Employee authorizedBy, DateTime at)
    {
        // 9
        ...
    }
}

public class CashDropRepository // The implementation is in the Data Access Layer
{
    public void Add(CashDrop item)
    {
        // 10
        ...
    }
}

Kodda yer alan doğrulama kontrollerini gördüğüm 10 konum belirtmiştim. Benim sorum, aşağıdaki iş kuralları (uzunluk, aralık, biçim, tür, vb. İçin standart denetimlerle birlikte) verildiğinde, her biri için ne tür kontroller yapacağınızdır:

  1. Nakit düşüş tutarı sıfırdan büyük olmalıdır.
  2. Nakit düşüşünün geçerli bir Sürücüsü olmalıdır.
  3. Geçerli kullanıcının nakit damlası ekleme yetkisi olmalıdır (geçerli kullanıcı sürücü değildir).

Lütfen düşüncelerinizi, bu senaryoya nasıl yaklaştığınız veya yaklaşacağınızı ve seçimlerinizin nedenlerini paylaşın.


SE, "teorik ve öznel bir tartışmayı teşvik etmek" için doğru bir platform değildir. Kapatmak için oylama.
tdammers

Kötü ifade ifadesi. Gerçekten en iyi uygulamaları arıyorum.
SonOfPirate

2
@tdammers - Evet, doğru yer. En azından olmak istiyor. SSS bölümünden: 'Öznel sorulara izin verilir.' Bu yüzden Stack Overflow yerine bu siteyi yaptılar. Yakın Nazi olma. Soru berbatsa, belirsizliğe dönüşecektir.
FastAl

@FastAI: Bu 'öznel' kısım değil, beni rahatsız eden 'tartışma'.
tdammers

Ben bir CashDropAmountdeğer yerine bir değer nesnesi alarak burada değer nesneleri kaldırabileceğini düşünüyorum Decimal. Sürücünün var olup olmadığını kontrol etmek, komut işleyicide yapılacaktır ve aynısı yetkilendirme kuralları için de geçerlidir. Approver approver = approverService.findById(employeeId)Çalışan onaylayan rolündeyse nereye atar gibi bir şey yaparak ücretsiz izin alabilirsiniz . Approverbir varlık değil, sadece bir değer nesnesi olur. Ayrıca bunun yerine bir AR üzerinde fabrika veya kullanım fabrika yönteminin kurtulmak olabilir: cashDrop = driver.dropCash(...).
plalx

Yanıtlar:


2

Doğruladığınız şeyin uygulamanın her katmanında farklı olacağını kabul ediyorum. Genellikle sadece geçerli yöntemde kodu yürütmek için gerekli olanları doğrular. Alttaki bileşenleri kara kutular gibi ele almaya çalışıyorum ve bu bileşenlerin nasıl uygulandığına bağlı olarak doğrulama yapmıyorum.

Yani, bir örnek olarak, CashDropApi sınıfınızda, yalnızca 'sözleşme'nin boş olmadığını doğrularım. Bu NullReferenceExceptions önler ve bu yöntemin düzgün çalışmasını sağlamak için gereken tek şey.

Ben hizmet veya komut sınıflarında bir şey doğrulamak bilmiyorum ve işleyici sadece 'komut' CashDropApi sınıfındaki aynı nedenlerle boş olmadığını doğrular. Fabrika ve varlık sınıfları için her iki şekilde de doğrulama gördüm (ve yaptım). Biri veya diğeri, 'miktar'ın değerini doğrulamak istediğiniz ve diğer parametrelerin boş olmadığı (iş kurallarınız).

Havuz, yalnızca nesnede bulunan verilerin veritabanınızda tanımlanan şema ile tutarlı olduğunu ve daa işleminin başarılı olacağını doğrulamalıdır. Örneğin, boş bırakılamayan veya maksimum uzunluğu vb. Olan bir sütununuz varsa.

Güvenlik kontrolüne gelince, bunun gerçekten bir niyet meselesi olduğunu düşünüyorum. Kural, yetkisiz erişimi önlemeyi amaçladığından, kullanıcı yetkilendirilmezse attığım gereksiz adımların sayısını azaltmak için bu kontrolü mümkün olduğunca erken yapmak istiyorum. Muhtemelen CashDropApi'ye koyardım.


1

İlk iş kuralınız

Nakit düşüş tutarı sıfırdan büyük olmalıdır.

CashDropvarlığınızın ve AddCashDropCommandsınıfınızın değişmezine benziyor . Böyle bir değişmezi zorlamanın birkaç yolu var:

  1. Sözleşmeye Göre Tasarım rotasını kullanın ve durumunuza bağlı olarak Ön Koşullar, Son Koşullar ve bir [ContractInvariantMethod] kombinasyonu ile Kod Sözleşmelerini kullanın .
  2. 0'dan küçük bir miktarda iletirseniz, bir ArgumentException özel durumu oluşturan yapıcı / ayarlayıcılara açık kod yazın.

İkinci kuralınız daha geniştir (sorudaki ayrıntılar ışığında): Geçerli, Sürücü varlığının araç sürebileceklerini belirten (yani sürücü ehliyetinin askıya alınmadığını) gösteren bir bayrağa sahip olduğu anlamına mı gelir? aslında o gün çalışıyor ya da sadece CashDropApi'ye aktarılan driverId'ın kalıcılık deposunda geçerli olduğu anlamına mı geliyor?

Bu durumlarda , kod örneğinizde yaptığınız gibi alan adı modelinizde gezinmeniz ve Driverörneği IEmployeeRepositorysizden almanız location 4gerekir. Bu nedenle, burada depoya yapılan çağrının null döndürmediğinden emin olmanız gerekir, bu durumda driverId geçerli değildi ve işleme devam edemezsiniz.

Diğer 2 (varsayımsal) kontroller için (sürücünün geçerli bir sürücü ehliyeti var mı, bugün çalışan sürücü müydü) iş kurallarını uyguluyorsunuz.

Burada yapmaya eğilimli olduğum şey, varlıklar üzerinde çalışan bir validator sınıfı koleksiyonu kullanmaktır (tıpkı Eric Evans kitabındaki şartname modeli gibi - Domain Driven Design). Bu kuralları ve doğrulayıcıları oluşturmak için FluentValidation'ı kullandım . Daha basit kurallardan daha karmaşık / daha eksiksiz kurallar oluşturabilirim (ve dolayısıyla yeniden kullanabilirim). Ve mimarimdeki hangi katmanların onları çalıştıracağına karar verebilirim. Ama hepsinin tek bir yerde kodlanmış olması, sisteme dağılmamış olması.

Üçüncü kuralınız kesişen bir kaygı ile ilgilidir: yetkilendirme. Zaten bir IoC konteyneri kullandığınızdan (IoC konteynerinizin yöntem müdahalesini desteklediği varsayılarak) biraz AOP yapabilirsiniz . Yetkilendirmeyi yapan bir apsect yazın ve IoC kapsayıcısını olması gereken yere bu yetkilendirme davranışını enjekte etmek için kullanabilirsiniz. Buradaki büyük kazanç, mantığı bir kez yazmış olmanızdır, ancak sisteminizde yeniden kullanabilirsiniz.

Dinamik bir proxy (Castle Windsor, Spring.NET, Ninject 3.0, vb.) Yoluyla müdahaleyi kullanmak için hedef sınıfınızın bir arabirim uygulaması veya bir temel sınıftan miras alması gerekir. Hedef yönteme yapılan çağrıdan önce araya girer, kullanıcının yetkisini kontrol eder ve kullanıcının sahip olmadığı durumlarda çağrının gerçek yönteme geçmesini (bir hariç tutma, günlük, hatayı gösteren bir değer döndürür veya başka bir şey) engellersiniz işlemi gerçekleştirmek için doğru roller.

Sizin durumunuzda,

CashDropService.AddCashDrop(...) 

AddCashDropCommandHandler.Handle(...)

CashDropServiceArayüz / temel sınıf olmadığı için buradaki problemler ele geçirilemeyebilir. Ya AddCashDropCommandHandlerda IoC'niz tarafından oluşturulmuyor, bu nedenle IoC'niz aramayı durdurmak için dinamik bir proxy oluşturamıyor. Spring.NET, bir regex aracılığıyla bir derlemedeki bir sınıfta bir yöntemi hedefleyebileceğiniz kullanışlı bir özelliğe sahiptir, bu nedenle bu işe yarayabilir.

Umarım sana fikir verir.


"IoC kapsayıcısını olması gereken yere bu yetkilendirme davranışını enjekte etmek için nasıl kullanacağımı" açıklayabilir misiniz? Bu kulağa çekici geliyor ama AOP ve IoC'nin birlikte çalışmasını sağlamak şimdiye kadar kaçıyor.
SonOfPirate

Geri kalanlara gelince, nesnenin geçersiz bir duruma (değişmezleri işleme) girmesini önlemek için yapıcıya ve / veya ayarlayıcılara doğrulama yerleştirmeyi kabul ediyorum. Ancak bunun ötesinde ve sürücüyü bulmak için IEmployeeRepository'ye gittikten sonra null kontrole yapılan bir başvuru, doğrulama işleminin geri kalanını gerçekleştireceğiniz herhangi bir ayrıntı sağlamazsınız. FluentValidation ve sağladığı yeniden kullanım vb. Göz önüne alındığında, verilen modeldeki kuralları nereye uygularsınız?
SonOfPirate

Cevabımı düzenledim - bunun yardımcı olup olmadığını görün. "Verilen modeldeki kuralları nereye uygularsınız?"; muhtemelen komut işleyicinizde 4, 5, 6, 7 civarındadır. İşletme düzeyinde doğrulama gerçekleştirmek için ihtiyacınız olan bilgileri verebilecek depolara erişiminiz vardır. Ama bence burada benimle aynı fikirde olmayanlar var.
RobertMS

Açıklığa kavuşturmak için, tüm bağımlılıklar enjekte ediliyor. Referans kodunu kısa tutmak için bunu kapalı bıraktım. Sorularım konteynır yoluyla enjekte edilmediğinden, araştırmamın bu konuya bağımlılık ile ilgisi var. Peki, AuthorizationAspect, örneğin AuthorizationService'e nasıl bir referans alır?
SonOfPirate

1

Kurallar için:

1- Nakit düşüş tutarı sıfırdan büyük olmalıdır.

2- Nakit düşüşünde geçerli bir Sürücü bulunmalıdır.

3- Geçerli kullanıcının nakit damlası ekleme yetkisi olmalıdır (geçerli kullanıcı sürücü değildir).

İş kuralı (1) için konum (1) 'de doğrulama yapacağım ve kural (2) için ön kontrol olarak Kimlik değerinin boş veya negatif olmadığından (sıfırın geçerli olduğu varsayılarak) emin olun. Bunun nedeni, "Mevcut bilgilerle kontrol edebileceğiniz yanlış verilerle katman sınırını geçme" kuralıdır. Bunun bir istisnası, hizmetin doğrulama işlemini diğer arayanlara yönelik görevinin bir parçası olarak yapmasıdır. Bu durumda, validasyonun sadece orada olması yeterli olacaktır.

Kural (2) ve (3) için, veritabanı erişim katmanında (veya db katmanının kendisinde) yalnızca db erişimi içerdiği için yapılmalıdır. Kasıtlı olarak katmanlar arasında seyahat etmeye gerek yoktur.

GUI'nin yetkisiz kullanıcıların bu senaryoyu etkinleştiren düğmeye basmasını engellemesine izin verirsek özellikle kural (3) önlenebilir. Bu kodlamak daha zor olsa da, daha iyidir.

İyi soru!


Yetkilendirme için +1 - bunu kullanıcı arayüzüne yerleştirmek cevabımda bahsetmediğim bir alternatif.
RobertMS

Kullanıcı arayüzünde yetkilendirme kontrollerine sahip olmak kullanıcı için daha etkileşimli bir deneyim sağlarken, hizmet tabanlı bir API geliştiriyorum ve arayanın hangi kuralları uyguladığı veya uygulamadığı hakkında herhangi bir varsayımda bulunamıyorum. Çünkü bu kontrollerin birçoğu, arayüz için temel olarak API projesini kullanmayı seçtiğim kullanıcı arayüzüne kolayca devredilebilir. Hızlı ve kolay bir ders kitabı yerine en iyi uygulamaları arıyorum.
SonOfPirate

@SonOfPirate, INMO, kullanıcı arayüzü daha hızlı olduğundan ve hizmetten daha fazla veri içerdiğinden (bazı durumlarda) doğrulamalar yapmalıdır. Artık hizmet, istemciye güvenmemesini istediğiniz sürece sorumluluklarının bir parçası olduğu için kendi doğrulamalarını yapmadan kendi sınırları dışında veri göndermemelidir. Buna göre, daha fazla işlem için veri tabanına veri göndermeden önce hizmette (tekrar) db olmayan kontrollerin yapılmasını öneririm.
NoChance
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.