【问题标题】:Localizing validation messages from Domain Objects (Entities)本地化来自域对象(实体)的验证消息
【发布时间】:2012-04-23 06:42:11
【问题描述】:

我的目的不是就 DDD 中的验证、代码所属的位置等进行辩论,而是专注于一种可能的方法以及如何解决本地化问题。我在我的一个域对象(实体)上具有以下行为(方法),它举例说明了这种情况:

public void ClockIn()
{
    if (WasTerminated)
    {
        throw new InvalidOperationException("Cannot clock-in a terminated employee.");
    }

    ClockedInAt = DateTime.Now;
    :
}

如您所见,当调用 ClockIn 方法时,该方法会检查对象的状态以确保 Employee 没有被终止。如果 Employee 被终止,我们会抛出一个与“不要让你的实体进入无效状态”方法一致的异常。

我的问题是我需要本地化异常消息。这通常使用应用程序服务 (ILocalizationService) 完成(在此应用程序中),该应用程序服务是使用 MEF 在需要访问其方法的类中导入的。但是,与任何 DI 框架一样,只有在容器实例化对象时才会注入/导入依赖项。 DDD 通常不是这种情况。

此外,我所了解的有关 DDD 的所有内容都表明,我们的域对象不应该有依赖关系,这些问题应该在域对象的外部处理。如果是这种情况,我该如何对如上所示的消息进行本地化?

这并不是一个新要求,因为许多业务应用程序都需要全球化/本地化。我会很感激一些建议如何使这项工作仍然与 DDD 的目标保持一致。

更新

我最初没有指出我们的本地化都是由数据库驱动的,所以我们确实有一个本地化服务(通过可注入的 ILocalizationService 接口)。因此,使用 Visual Studio 作为项目一部分提供的静态资源类并不是一个可行的选择。

另一个更新

也许它会推动讨论,说明该应用程序是一个 RESTful 服务应用程序。因此,客户端可以是一个简单的网络浏览器。因此,我不能期望调用者可以执行任何类型的本地化、代码映射等进行编码。当发生异常时(在这种方法中,尝试将域对象置于无效状态是异常),抛出异常并返回相应的 HTTP 状态代码以及异常消息,该异常消息应本地化为调用者的文化(Accept-Language)。

【问题讨论】:

标签: localization dependency-injection domain-driven-design globalization


【解决方案1】:

如果本地化是域/应用程序的重要组成部分,您应该将其设为一等公民并注入它所属的任何地方。我不确定“DDD 说我们的域对象不应该有依赖项”是什么意思 - 请解释一下。

【讨论】:

  • 在“Domain Object Dependencies”、“DDD Entity Dependencies”或任何类似的变体上进行网络搜索,您会发现大量关于此主题的文章、帖子和讨论。而且,根据埃文斯的说法,这根本不是好的做法。
【解决方案2】:

不确定此回复对您有多大帮助,但本地化确实是前端问题。根据您的示例本地化异常消息不是常见的做法,因为最终用户不应该看到技术细节,例如异常消息中描述的那些(并且任何将对您的异常进行故障排除的人可能具有足够的英语水平,即使它不是他们的母语)。

当然,如有必要,您始终可以处理异常并在前端向您的用户提供本地化、用户友好的消息。但将其作为前端关注点应该会简化您的架构。

【讨论】:

  • 要求是在发生异常时向 UI 显示描述性消息。这必须与 UI 中显示的其余文本一起本地化。它不能简单地是 UI 问题,原因有两个: 1. 数据存储在数据库中,因此必须有信息的后端提供者;并且,2. 对于每个可能的异常,它都需要一个唯一的异常类型,以便客户端可以捕获它并查找本地化文本。相反,由于服务器具有本地化文本,因此在服务器上执行本地化会更干净。
  • 例如,第二条业务规则是不为已经打卡的员工打卡。这也会导致抛出 InvalidOperationException,但会出现与如上所示。所以我要么在这里本地化它,在那里我控制我想要的消息,要么我必须创建两个异常类来代表每种情况。当应用于我正在开发的需要执行数百个业务规则的真实企业级应用程序时,这将变得非常庞大。
  • 听起来您可能过度依赖异常。验证逻辑不必通过异常来实现。您可以返回已知的错误代码并将它们映射到资源标识符,以便您可以在 UI 中显示本地化消息。
  • 我们并没有过度依赖异常。例外情况是例外情况,尝试为被解雇的员工打卡当然是例外情况。返回错误代码是一种有据可查的不良做法,我们不会采用这种做法。
  • 在较低层为无效状态/输入抛出异常是好的(抛出异常的断言对此很有用),但 UI 和验证逻辑应防止在第一名。一个好的用户界面不应该让被解雇的员工打卡。
【解决方案3】:

正如 Clafou 所说,您不应该使用异常以任何方式向 UI 传递消息。

如果您仍然坚持这样做,一种选择是抛出错误代码而不是消息

throw new InvalidOperationException("ERROR_TERMINATED_EMPLOYEE_CLOCKIN");

然后,当它发生时,对异常做任何你需要做的事情(日志、查找本地化等等)。

【讨论】:

  • 正如我对@Clafou 所说,例外是针对特殊情况的,这就是我所描述的。我曾考虑将资源键设置为异常消息,但这似乎有点像将方形钉塞入圆孔中。如果可能的话,我想找一个方孔。
【解决方案4】:

您尝试避免向域模型对象添加内部依赖项是正确的。

更好的解决方案是在服务方法内部处理操作,例如:

public class EmployeeServiceImpl implements EmployeeService {

   public void ClockEmployeeIn(Employee employee) throws InvalidOperationException {
      if (employee.isTerminated()) {
          // Localize using a resource lookup code..
          throw new InvalidOperationException("Error_Clockin_Employee_Terminated");      
      }       
      employee.setClockedInAt(DateTime.Now);
   }
}

然后,您可以使用 DI 框架在您将进行时钟调用的地方注入服务,并使用该服务将您的域对象与业务逻辑的更改隔离开来。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多