论坛首页 Java企业应用论坛

真能做到完全分离Domain Object 和 DAO,而又不使Business L...

浏览 12784 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2005-01-28  
主题:真能做到完全分离Domain Object 和 DAO,而又不使Business Logic外泄么?

这是我目前项目中遇到的问题。大致描述一下。

业务逻辑:
系统包括若干机构,机构中可以建立和删除用户,业务限制同一个机构内用户名不能相同。

Domain model如下:
Organ  <>------------> User
    1     0..*

按rich domain model,我能想到的比较自然的方式是将
public User createUser(String name); throws UserAlreadyExistException

方法放到Organ中。

但是在该方法中,如何判断用户名已经存在呢?如果仔细分析对象职责,其实这个判断应该放到User的构造函数里面
User(Organ organ, String name); throws UserAlreadyExistException

我目前的做法是在User的构造函数中访问DAO。

而这种Domain object 直接使用 DAO的情形,好像在hibernate in action (参见8.1.1后半部分)中是被极力避免的。难道只能是 使用create User之前在action中调用DAO先判断一下么? 那么throw new UserAlreadyExistException()这个逻辑也只能放到action中么?
   发表时间:2005-01-28  
不明白。
你的User会一直隐藏在Organ之后吗?
按照你的模型是整体-部分的关系。这要求外面的其他业务对象不能直接同
User关联。而必须通过Organ来操作User。
我不赞同这种设计。
整体部分的关系用在如:Order、OrderItem的关系下是合适的。

业务创建同对象的构造函数是两回事。
另外我认为没必要把客户已存在的检查放在User的构造函数中,
你的需求是在“创建客户”时检查是否User已存在。这同在构造函数中实现
并不等价。因为这样就不得不在从数据库中重新构建User对象时也执行该检查。

假如这种检查只发生在开户中,建议该逻辑就放在开户的控制类中。
0 请登录后投票
   发表时间:2005-01-28  
干嘛要分离,我都是直接在类里调用dao。
0 请登录后投票
   发表时间:2005-01-29  
首先感谢 partech 和 towjzhou 能得到你们的回答我真的很高兴,希望能继续讨论。
我目前的基本业务是这样,User不能独立于Organ存在。

不明白"业务创建"是指什么?

可能这里面有这样一个理解上的问题,即Domain model是否对外(各种Client的编写者)开放?
我目前理解中的Domain model是开放的,因此我不能允许先new一个User,而不管该User是否合乎业务要求(User必从属于Organ,且在Organ内不能重名)。

如果将“用户名在该机构内是否唯一”的检查放入一个控制类,那是不是将Domain Object 的方法和数据分离了。这样对Domain Object的使用都要经过控制类?Domain Object 内无业务或者只有少量业务? 如果有若干这样的Domain Object,是否也需要相应的建立若干个控制类呢?

另外我理解开户是一个Use case logic,好像比这个“用户名在该机构内是否唯一”的逻辑更大,是一个流程了,而且可能针对不同的Client,这个流程还不太一样。

在该应用中,使用了hibernate,因此从数据库中读取时(find)使用的构造函数与程序内使用的并不相同。

最后附上 createUser 方法如下:
Organ 中:
    public User createUser(String name);
        throws UserNameAlreadyExistException
    {

        User user = new User(this, name);;

        if (this._users.add(user););
        {
            DAO.save(user);;
        }

        return user;
    }

User 中:
    private User(); //for hibernate
    {
        super();;
    }

    User(Organ organ, String name);
        throws UserNameAlreadyExistException
    {

        super();;

        this._organ = organ;

        this._setName(name);;
    }

    public void setName(String name);
        throws UserNameAlreadyExistException
    {

        if (!Checker.checkName(name););
        {
            throw new IllegalNameException(name);;
        }

        if (name.equals(this._name););
        {
            return;
        }

        this.checkDuplicateName(this._organ, name);;

        this._name = name;
    }

    private void checkDuplicateName(Organ organ, String name);
        throws UserNameAlreadyExistException
    {

        User u = DAO.find(organ, name);;

        if (null != u);
        {
            throw new UserNameAlreadyExistException(name);;
        }
    }
0 请登录后投票
   发表时间:2005-01-29  
业务创建就是指从业务上看创建了某个业务对象。例如:开户。
相反对象的构造函数是在计算机的内存里创建某个对象。它们的含义是不同的。
你现在把他们混在一起,是不合适的。
我认为检查是否机构中已经存在了某个用户只需要在开户时检查就行了。
所以只需要封装在开户的控制类中。
0 请登录后投票
   发表时间:2005-01-29  
partech 写道
业务创建就是指从业务上看创建了某个业务对象。例如:开户。
相反对象的构造函数是在计算机的内存里创建某个对象。它们的含义是不同的。
你现在把他们混在一起,是不合适的。
我认为检查是否机构中已经存在了某个用户只需要在开户时检查就行了。
所以只需要封装在开户的控制类中。

估计是老兄你没完全理解O/R。
0 请登录后投票
   发表时间:2005-01-29  
pig345 写道

可能这里面有这样一个理解上的问题,即Domain model是否对外(各种Client的编写者)开放?
我目前理解中的Domain model是开放的,因此我不能允许先new一个User,而不管该User是否合乎业务要求(User必从属于Organ,且在Organ内不能重名)。

开放Domain Model可不是个好的主意,会带来各种问题,而且Client也不定是java,如果是.net或是js呢,所以还是弄一层很博的服务层把客户请求转发给DomainModel.
0 请登录后投票
   发表时间:2005-01-29  
再次感谢partech和towjzhou

刚看过partech的Domain Model 探索,对其中描述的东西,有一点了解(代码太多,没仔细看完 )。
但是很奇怪,partech对我的问题会这样回答,我感觉 这个开户的控制类 是一个明显的transaction script,是针对Use case logic的。

另外:to towjzhou

我其实不是直接暴露Domain model 层,对Domain object 的使用是要经过一个Service action的包装的。 我的意思是,针对不同client提供的Service action是不同的,如果不把逻辑放在Domain object里面,势必会在多个Service action中包含相同的代码。但是放在Domain object里就产生了对DAO的依赖,虽然我是通过接口和工厂使用DAO的。不过现在想起来《企业应用架构模式》里面也是提到过这样用的,只是Hibernate in action里面是极力避免的。
而且在各Domain object之间产生比较强的依赖,这也是我感觉不舒服的地方。
0 请登录后投票
   发表时间:2005-01-29  
pig345 写道
再次感谢partech和towjzhou

刚看过partech的Domain Model 探索,对其中描述的东西,有一点了解(代码太多,没仔细看完 )。
但是很奇怪,partech对我的问题会这样回答,我感觉 这个开户的控制类 是一个明显的transaction script,是针对Use case logic的。


towjzhou 写道

估计是老兄你没完全理解O/R。


huhu,不好找到已发贴得url.重贴一个。

业务对象的持久化

一个会引起争议的问题,是业务层是否会涉及业务对象持久化的概念。
答案是肯定的。
DDD中在描述The life cycle of a domain object时,给出了两种形式的持久化。
Store和Archive。我们使用的较多是Store。

但是这不代表业务层要依赖数据访问层。相反依赖关系应该倒过来。数据访问层依赖
业务层。通常我们使用Mapper实现,在hibernate中通过配置达到该目的。
要做到业务层不依赖于数据访问层,同样借助接口来完成。
在业务层定义数据访问的接口,为了方便,可以使用一个类来封装这些操作。

public interface CustomerFinder
{
Customer findByID(ID id);
Customer findByCode(String code);
DomainObjectCollection findByName(String name);
...
}

public class CustomerRepository
{
private static CustomerFinder finder = null;
private static CustomerFinder getFinderInstance()
{
if (finder == null)
{
finder = (CustomerFinder)FinderRegistry.getFinder("CustomerFinder");
}
return finder;
}

public static Customer findByID(ID id)
{
Customer obj = getFinderInstance().findByID(id);
Check.require(obj != null,
"未找到ID为:" + id.toString() +
"对应的 Customer。");
return obj;
}
...
}

在数据访问层实现这些接口。因为是数据访问层依赖业务层,所以你可以采用多种技术来实现,
使用hibernate这样的开源项目,或者手工编写Mapper。

pig345 写道
再次感谢partech和towjzhou

刚看过partech的Domain Model 探索,对其中描述的东西,有一点了解(代码太多,没仔细看完 )。
但是很奇怪,partech对我的问题会这样回答,我感觉 这个开户的控制类 是一个明显的transaction script,是针对Use case logic的。


关于开户的问题,开户最终的处理不是属于Use case的,因为假如你有多种
应用同时需要开户时,最终开户的业务规则应该是一样的,但是中间的交互过程
可能会不同。
比如:开户我可以通过部门的管理人员通过他使用的程序开进去。如果公司提供了网上开户的功能,我就可以通过上网,自己就搞定了。但是无论我选择哪种方式。客户名称都不允许重复,这条业务规则都是要遵守的。

不知道说明白了没有。
0 请登录后投票
   发表时间:2005-01-29  
引用
但是这不代表业务层要依赖数据访问层。相反依赖关系应该倒过来。数据访问层依赖
业务层。通常我们使用Mapper实现,在hibernate中通过配置达到该目的。
要做到业务层不依赖于数据访问层,同样借助接口来完成。
在业务层定义数据访问的接口,为了方便,可以使用一个类来封装这些操作。

public interface CustomerFinder
{
Customer findByID(ID id);
Customer findByCode(String code);
DomainObjectCollection findByName(String name);
...
}

public class CustomerRepository
{
private static CustomerFinder finder = null;
private static CustomerFinder getFinderInstance()
{
if (finder == null)
{
finder = (CustomerFinder)FinderRegistry.getFinder("CustomerFinder");
}
return finder;
}

public static Customer findByID(ID id)
{
Customer obj = getFinderInstance().findByID(id);
Check.require(obj != null,
"未找到ID为:" + id.toString() +
"对应的 Customer。");
return obj;
}
...
}

在数据访问层实现这些接口。因为是数据访问层依赖业务层,所以你可以采用多种技术来实现,
使用hibernate这样的开源项目,或者手工编写Mapper。



"在业务层定义数据访问的接口"    把Mapper的接口放在Business层 ,实现放在Mapper层,这样接口和视线分开在2个不同的Package中,总觉得怪怪的,我一般是把Mapper的接口和Impl放在同一个Package下,但是这样作确实导致了Business层和Mapper层的循环依赖
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics