Bu tasarım uygun DDD'ye nasıl daha yakın hale getirilir?


12

DDD'yi günlerdir okudum ve bu örnek tasarım konusunda yardıma ihtiyacım var. DDD'nin tüm kuralları beni, etki alanı nesnelerinin uygulama katmanına yöntemler göstermesine izin verilmediğinde nasıl bir şey inşa etmem gerektiği konusunda kafamı karıştırıyor; davranışı düzenlemek için başka nerede? Depoların varlıklara enjekte edilmesine izin verilmez ve bu nedenle varlıkların kendileri devlet üzerinde çalışmalıdır. Sonra bir varlık etki alanından başka bir şey bilmek gerekir, ancak diğer varlık nesneleri de enjekte izin verilmiyor? Bunlardan bazıları benim için anlamlı, bazıları anlamsız. Tüm örneklerin Siparişler ve Ürünler ile ilgili olduğu gibi, tüm örnekleri nasıl oluşturacağınıza dair iyi örnekler bulamadım, diğer örnekleri tekrar tekrar tekrarladım. En iyi örnekleri okuyarak öğreniyorum ve bugüne kadar DDD hakkında edindiğim bilgileri kullanarak bir özellik oluşturmaya çalıştım.

Ne yanlış yaptığımı ve nasıl düzeltebileceğinizi belirtmek için yardımınıza ihtiyacım var, en iyisi "Ben X ve Y yapıyor tavsiye etmem" olarak kod ile her şey zaten sadece belirsiz bir şekilde tanımlanmış bir bağlamda anlamak çok zor. Eğer bir objeyi bir başkasına enjekte edemezsem, onu nasıl düzgün bir şekilde yapacağımı görmek daha kolay olurdu.

Örneğimde kullanıcılar ve moderatörler var. Bir moderatör kullanıcıları yasaklayabilir, ancak bir iş kuralıyla: günde sadece 3. İlişkileri (aşağıdaki kodu) göstermek için bir sınıf diyagramı kurma girişiminde bulundum:

resim açıklamasını buraya girin

interface iUser
{
    public function getUserId();
    public function getUsername();
}

class User implements iUser
{
    protected $_id;
    protected $_username;

    public function __construct(UserId $user_id, Username $username)
    {
        $this->_id          = $user_id;
        $this->_username    = $username;
    }

    public function getUserId()
    {
        return $this->_id;
    }

    public function getUsername()
    {
        return $this->_username;
    }
}

class Moderator extends User
{
    protected $_ban_count;
    protected $_last_ban_date;

    public function __construct(UserBanCount $ban_count, SimpleDate $last_ban_date)
    {
        $this->_ban_count       = $ban_count;
        $this->_last_ban_date   = $last_ban_date;
    }

    public function banUser(iUser &$user, iBannedUser &$banned_user)
    {
        if (! $this->_isAllowedToBan()) {
            throw new DomainException('You are not allowed to ban more users today.');
        }

        if (date('d.m.Y') != $this->_last_ban_date->getValue()) {
            $this->_ban_count = 0;
        }

        $this->_ban_count++;

        $date_banned        = date('d.m.Y');
        $expiration_date    = date('d.m.Y', strtotime('+1 week'));

        $banned_user->add($user->getUserId(), new SimpleDate($date_banned), new SimpleDate($expiration_date));
    }

    protected function _isAllowedToBan()
    {
        if ($this->_ban_count >= 3 AND date('d.m.Y') == $this->_last_ban_date->getValue()) {
            return false;
        }

        return true;
    }
}

interface iBannedUser
{
    public function add(UserId $user_id, SimpleDate $date_banned, SimpleDate $expiration_date);
    public function remove();
}

class BannedUser implements iBannedUser
{
    protected $_user_id;
    protected $_date_banned;
    protected $_expiration_date;

    public function __construct(UserId $user_id, SimpleDate $date_banned, SimpleDate $expiration_date)
    {
        $this->_user_id         = $user_id;
        $this->_date_banned     = $date_banned;
        $this->_expiration_date = $expiration_date;
    }

    public function add(UserId $user_id, SimpleDate $date_banned, SimpleDate $expiration_date)
    {
        $this->_user_id         = $user_id;
        $this->_date_banned     = $date_banned;
        $this->_expiration_date = $expiration_date;
    }

    public function remove()
    {
        $this->_user_id         = '';
        $this->_date_banned     = '';
        $this->_expiration_date = '';
    }
}

// Gathers objects
$user_repo = new UserRepository();
$evil_user = $user_repo->findById(123);

$moderator_repo = new ModeratorRepository();
$moderator = $moderator_repo->findById(1337);

$banned_user_factory = new BannedUserFactory();
$banned_user = $banned_user_factory->build();

// Performs ban
$moderator->banUser($evil_user, $banned_user);

// Saves objects to database
$user_repo->store($evil_user);
$moderator_repo->store($moderator);

$banned_user_repo = new BannedUserRepository();
$banned_user_repo->store($banned_user);

Kullanıcı yetkisinde 'is_banned'kontrol edilebilecek bir alan olmalı $user->isBanned();mı? Yasak nasıl kaldırılır? Hiç bir fikrim yok.


Wikipedia makalesinden: "Etki alanına dayalı tasarım bir teknoloji veya metodoloji değildir." Ayrıca, modelinizin doğru olup olmadığına yalnızca siz ve 'uzmanlarınız' karar verebilirsiniz.

1
@Todd smith, "etki alanı nesnelerinin uygulama katmanına yöntem göstermesine izin verilmiyor" üzerinde büyük bir noktaya değiniyor . Etki alanı nesnelerine depo enjekte etmemenin anahtarı olan ilk kod örneğinin, başka bir şeyin kaydedip yüklediğine dikkat edin. Bunu kendileri yapmıyorlar. Bu, uygulama mantığının etki alanı / model / varlık / iş nesneleri / veya bunları aramak istediğiniz her şey yerine işlemlerini kontrol etmesine olanak tanır.
FastAl

Yanıtlar:


11

Bu soru biraz özneldir ve bir başkasının işaret ettiği gibi, yığın akışı biçimi için uygun olmayan doğrudan bir cevaptan daha fazla tartışmaya yol açar. Bununla birlikte, sadece sorunların nasıl çözüleceğine dair bazı kodlanmış örneklere ihtiyacınız olduğunu söyledi, bu yüzden size bazı fikirler vermek için bir şans vereceğim.

Söyleyeceğim ilk şey:

"etki alanı nesnelerinin uygulama katmanına yöntem göstermesine izin verilmiyor"

Bu doğru değil - bunu nereden okuduğunuzu bilmek isterdim. Uygulama katmanı, kullanıcı arayüzü, Altyapı ve Etki Alanı arasındaki orkestratördür ve bu nedenle açıkça etki alanı varlıkları üzerindeki yöntemleri çağırması gerekir.

Sorununuzu nasıl çözeceğime dair kodlanmış bir örnek yazdım. Ben C # özür dileriz, ama PHP bilmiyorum - umarım hala bir yapı perspektifinden gist alacaksınız.

Belki yapmamalıydım, ancak etki alanı nesnelerinizi biraz değiştirdim. Yasakın süresi dolmuş olsa bile, sistemde bir 'BannedUser' kavramının mevcut olması nedeniyle, biraz kusurlu olduğunu hissetmemize yardımcı olamadım.

Başlamak için, uygulama hizmeti - UI'nin aradığı şey:

public class ModeratorApplicationService
{
    private IUserRepository _userRepository;
    private IModeratorRepository _moderatorRepository;

    public void BanUser(Guid moderatorId, Guid userToBeBannedId)
    {
        Moderator moderator = _moderatorRepository.GetById(moderatorId);
        User userToBeBanned = _userRepository.GetById(userToBeBannedId);

        using (IUnitOfWork unitOfWork = UnitOfWorkFactory.Create())
        {
            userToBeBanned.Ban(moderator);

            _userRepository.Save(userToBeBanned);
            _moderatorRepository.Save(moderator);
        }
    }
}

Oldukça düz ileri. Denetleyiciyi yasaklamayı, denetleyicinin yasaklamak istediği kullanıcıyı getirir ve denetleyiciyi geçirerek kullanıcı üzerindeki 'Yasak' yöntemini çağırırsınız. Bu, daha sonra ilgili depoları aracılığıyla kalıcı olması gereken hem moderatörün hem de kullanıcının (aşağıda açıklanmıştır) durumunu değiştirecektir.

Kullanıcı sınıfı:

public class User : IUser
{
    private readonly Guid _userId;
    private readonly string _userName;
    private readonly List<ServingBan> _servingBans = new List<ServingBan>();

    public Guid UserId
    {
        get { return _userId; }
    }

    public string Username
    {
        get { return _userName; }
    }

    public void Ban(Moderator bannedByModerator)
    {
        IssuedBan issuedBan = bannedByModerator.IssueBan(this);

        _servingBans.Add(new ServingBan(bannedByModerator.UserId, issuedBan.BanDate, issuedBan.BanExpiry));
    }

    public bool IsBanned()
    {
        return (_servingBans.FindAll(CurrentBans).Count > 0);
    }

    public User(Guid userId, string userName)
    {
        _userId = userId;
        _userName = userName;
    }

    private bool CurrentBans(ServingBan ban)
    {
        return (ban.BanExpiry > DateTime.Now);
    }

}

public class ServingBan
{
    private readonly DateTime _banDate;
    private readonly DateTime _banExpiry;
    private readonly Guid _bannedByModeratorId;

    public DateTime BanDate
    {
        get { return _banDate;}
    }

    public DateTime BanExpiry
    {
        get { return _banExpiry; }
    }

    public ServingBan(Guid bannedByModeratorId, DateTime banDate, DateTime banExpiry)
    {
        _bannedByModeratorId = bannedByModeratorId;
        _banDate = banDate;
        _banExpiry = banExpiry;
    }
}

Bir kullanıcının değişmezi, yasaklandığında belirli eylemleri gerçekleştirememesidir, bu nedenle bir kullanıcının şu anda yasaklı olup olmadığını belirleyebilmemiz gerekir. Bunu başarmak için kullanıcı, moderatörler tarafından verilen servis yasaklarının bir listesini tutar. IsBanned () yöntemi henüz süresi dolmamış sunum yasaklarını kontrol eder. Ban () yöntemi çağrıldığında, parametre olarak bir moderatör alır. Bu daha sonra moderatörden bir yasak çıkarmasını ister:

public class Moderator : User
{
    private readonly List<IssuedBan> _issuedbans = new List<IssuedBan>();

    public bool CanBan()
    {
        return (_issuedbans.FindAll(BansWithTodaysDate).Count < 3);
    }

    public IssuedBan IssueBan(User user)
    {
        if (!CanBan())
            throw new InvalidOperationException("Ban limit for today has been exceeded");

        IssuedBan issuedBan = new IssuedBan(user.UserId, DateTime.Now, DateTime.Now.AddDays(7));

        _issuedbans.Add(issuedBan); 

        return issuedBan;
    }

    private bool BansWithTodaysDate(IssuedBan ban)
    {
        return (ban.BanDate.Date == DateTime.Today.Date);
    }
}

public class IssuedBan
{
    private readonly Guid _bannedUserId;
    private readonly DateTime _banDate;
    private readonly DateTime _banExpiry;

    public DateTime BanDate { get { return _banDate;}}

    public DateTime BanExpiry { get { return _banExpiry;}}

    public IssuedBan(Guid bannedUserId, DateTime banDate, DateTime banExpiry)
    {
        _bannedUserId = bannedUserId;
        _banDate = banDate;
        _banExpiry = banExpiry;
    }
}

Moderatör için değişmez, günde sadece 3 yasak verebilmesidir. Böylece, IssueBan yöntemi çağrıldığında, moderatörün verilen yasaklar listesinde bugünün tarihi ile 3 adet yasaklanmış yasak olup olmadığını kontrol eder. Daha sonra yeni verilen yasağı listesine ekler ve döndürür.

Öznel, ve eminim birisi yaklaşıma katılmayacaktır, ama umarım size bir fikir veya nasıl bir araya getirebileceği hakkında fikir verir.


1

Durumu değiştiren tüm mantığınızı hem Varlıklar hem de Depolar hakkında bilgi sahibi olan bir hizmet katmanına (örn: ModeratorService) taşıyın.

ModeratorService.BanUser(User, UserBanRepository, etc.)
{
    // handle ban logic in the ModeratorService
    // update User object
    // update repository
}
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.