इस डिजाइन को उचित डीडीडी के करीब कैसे बनाया जाए?


12

मैंने अभी दिनों के लिए DDD के बारे में पढ़ा है और इस नमूने के डिजाइन के साथ मदद की ज़रूरत है। डीडीडी के सभी नियम मुझे बहुत उलझन में डालते हैं कि मैं कैसे कुछ भी निर्माण करने वाला हूं जब डोमेन ऑब्जेक्ट को एप्लिकेशन परत को विधियों को दिखाने की अनुमति नहीं है; और कहां से आर्केस्ट्रा का व्यवहार? भंडार को संस्थाओं में इंजेक्ट करने की अनुमति नहीं है और संस्थाओं को स्वयं इस प्रकार राज्य पर काम करना चाहिए। फिर एक इकाई को डोमेन से कुछ और जानने की आवश्यकता होती है, लेकिन अन्य इकाई वस्तुओं को इंजेक्शन लगाने की अनुमति नहीं है? इनमें से कुछ चीजें मुझे समझ में आती हैं लेकिन कुछ नहीं। मुझे अभी तक अच्छे उदाहरण नहीं मिले हैं कि कैसे एक संपूर्ण फीचर का निर्माण किया जाए क्योंकि हर उदाहरण ऑर्डर्स एंड प्रोडक्ट्स के बारे में है, अन्य उदाहरणों को बार-बार दोहराते हैं। मैं उदाहरणों को पढ़कर सबसे अच्छा सीखता हूं और मैंने अभी तक DDD के बारे में जो जानकारी प्राप्त की है उसका उपयोग करके एक फीचर बनाने की कोशिश की है।

मुझे यह बताने में आपकी सहायता की आवश्यकता है कि मैं क्या गलत करता हूं और इसे कैसे ठीक करूं, सबसे अधिमानतः कोड के साथ "मैं एक्स और वाई करने के लिए पुनर्मिलन नहीं करूंगा" एक संदर्भ में समझना बहुत कठिन है जहां सब कुछ पहले से ही परिभाषित है। अगर मैं एक इकाई को दूसरे में इंजेक्ट नहीं कर सकता, तो यह देखना आसान होगा कि इसे ठीक से कैसे किया जाए।

मेरे उदाहरण में उपयोगकर्ता और मध्यस्थ हैं। एक मध्यस्थ उपयोगकर्ताओं पर प्रतिबंध लगा सकता है, लेकिन एक व्यापार नियम के साथ: केवल 3 प्रति दिन। मैंने रिश्तों को दिखाने के लिए एक वर्ग आरेख स्थापित करने का प्रयास किया (नीचे कोड):

यहाँ छवि विवरण दर्ज करें

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);

क्या उपयोगकर्ता की पात्रता में एक 'is_banned'फ़ील्ड होना चाहिए, जिसके साथ जाँच की जा सके $user->isBanned();? प्रतिबंध कैसे हटाएं? मुझे पता नहीं है।


विकिपीडिया लेख से: "डोमेन-संचालित डिज़ाइन एक तकनीक या एक कार्यप्रणाली नहीं है।" इस प्रकार इस तरह की चर्चा इस प्रारूप के लिए अनुपयुक्त है। इसके अलावा, केवल आप और आपके 'विशेषज्ञ' ही यह तय कर सकते हैं कि आपका मॉडल सही है या नहीं।

1
@Todd स्मिथ "डोमेन ऑब्जेक्ट्स को एप्लिकेशन लेयर में विधियां दिखाने की अनुमति नहीं है" पर एक शानदार बिंदु बनाता है । पहले कोड के नमूने पर ध्यान दें कि डोमेन ऑब्जेक्ट्स में रिपॉजिटरी को इंजेक्ट नहीं करने के लिए कुंजी है, कुछ और बचाता है और उन्हें लोड करता है। वे खुद ऐसा नहीं करते। यह एप्लिकेशन लॉजिक को डोमेन / मॉडल / इकाई / व्यावसायिक वस्तुओं / या जो भी आप उन्हें कॉल करना चाहते हैं, के बजाय लेनदेन को नियंत्रित करने देता है।
FastAl

जवाबों:


11

यह प्रश्न कुछ व्यक्तिपरक है और एक प्रत्यक्ष उत्तर की तुलना में अधिक चर्चा की ओर जाता है, जो किसी और ने इंगित किया है - स्टैकओवरफ़्लो प्रारूप के लिए उपयुक्त नहीं है। उस ने कहा, मुझे लगता है कि आपको समस्याओं से निपटने के लिए कुछ कोडित उदाहरणों की आवश्यकता है, इसलिए मैं इसे एक शॉट दूंगा, बस आपको कुछ विचार देने के लिए।

पहली बात मैं कहूंगा:

"डोमेन ऑब्जेक्ट्स को एप्लिकेशन लेयर के तरीकों को दिखाने की अनुमति नहीं है"

यह बस सच नहीं है - मुझे यह जानने में दिलचस्पी होगी कि आपने यह कहां से पढ़ा है। आवेदन परत UI, इन्फ्रास्ट्रक्चर और डोमेन के बीच ऑर्केस्ट्रेटर है, और इसलिए स्पष्ट रूप से डोमेन संस्थाओं पर तरीकों को लागू करने की आवश्यकता है।

मैंने एक कोडित उदाहरण लिखा है कि मैं आपकी समस्या से कैसे निपटूंगा। मैं माफी माँगता हूँ कि यह C # में है, लेकिन मुझे PHP का पता नहीं है - उम्मीद है कि आप अभी भी एक संरचना के नजरिए से समझ पाएंगे।

शायद मुझे नहीं करना चाहिए था, लेकिन मैंने आपके डोमेन ऑब्जेक्ट को थोड़ा संशोधित किया है। मैं यह महसूस करने में मदद नहीं कर सकता था कि यह थोड़ा त्रुटिपूर्ण था, इस प्रणाली में 'बैनडयूजर' की अवधारणा मौजूद है, भले ही प्रतिबंध समाप्त हो गया हो।

शुरू करने के लिए, यहां एप्लिकेशन सेवा है - यह वह है जिसे यूआई कॉल करेगा:

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);
        }
    }
}

बहुत सीधा। आप प्रतिबंध करने वाले मॉडरेटर को लाते हैं, उपयोगकर्ता जो प्रतिबंध करना चाहता है, और उपयोगकर्ता को 'प्रतिबंध' विधि देता है, जो मध्यस्थ को पारित करता है। यह दोनों मॉडरेटर और उपयोगकर्ता (नीचे समझाया गया) की स्थिति को संशोधित करेगा, जो तब उनके संबंधित रिपॉजिटरी के माध्यम से जारी रखने की आवश्यकता है।

उपयोगकर्ता वर्ग:

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;
    }
}

एक उपयोगकर्ता के लिए अपरिवर्तनीय यह है कि वे प्रतिबंधित होने पर कुछ कार्य नहीं कर सकते हैं, इसलिए हमें यह पहचानने में सक्षम होना चाहिए कि उपयोगकर्ता वर्तमान में प्रतिबंधित है या नहीं। इसे प्राप्त करने के लिए उपयोगकर्ता सेवारत प्रतिबंधों की एक सूची रखता है जो मध्यस्थों द्वारा जारी किए गए हैं। किसी भी सेवारत प्रतिबंधों के लिए IsBanned () पद्धति की जाँच अभी समाप्त होने वाली है। जब प्रतिबंध () विधि को कहा जाता है, तो यह एक पैरामीटर के रूप में एक मॉडरेटर को प्राप्त करता है। इसके बाद मॉडरेटर को प्रतिबंध जारी करने के लिए कहता है:

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;
    }
}

मध्यस्थ के लिए अपरिवर्तनीय यह है कि यह केवल 3 बैन प्रति दिन जारी कर सकता है। इस प्रकार, जब जारी करने की विधि को कॉल किया जाता है, तो यह जांचता है कि मॉडरेटर के पास जारी किए गए प्रतिबंधों की सूची में आज की तारीख के साथ 3 जारी किए गए प्रतिबंध नहीं हैं। यह तब जारी की गई सूची में नए जारी किए गए प्रतिबंध को जोड़ता है और इसे वापस करता है।

विषय, और मुझे यकीन है कि कोई व्यक्ति दृष्टिकोण से असहमत होगा, लेकिन उम्मीद है कि यह आपको एक विचार देता है या यह एक साथ कैसे फिट हो सकता है।


1

अपने सभी तर्कों को स्थानांतरित करें, जो राज्य को एक सेवा परत (पूर्व: मॉडरेटर सेवा) में बदल देता है, जो दोनों संस्थाओं और रिपॉजिटरी के बारे में जानता है।

ModeratorService.BanUser(User, UserBanRepository, etc.)
{
    // handle ban logic in the ModeratorService
    // update User object
    // update repository
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.