【发布时间】:2017-02-28 19:43:52
【问题描述】:
按照 DDD 实践,我在实现小型 AES 加密器/解密器(包装 .NET 的 AesCryptoServiceProvider)时遇到了问题。
public class Aes256CbcCryptor : ISymmetricCryptor
{
private SymmetricAlgorithm AesProvider { get; set; }
// Poor man's DI - beside the point
public Aes256Cbc()
{
this.AesProvider = new AesCryptoServiceProvider()
{
BlockSize = 128,
KeySize = 256,
Mode = CipherMode.CBC,
Padding = PaddingMode.PKCS7,
Key = // OH DEAR IT TAKES STATE
};
}
public Aes256Cbc(SymmetricAlgorithm aesProvider)
{
this.AesProvider = aesProvider;
}
public byte[] Encrypt(byte[] keyBytes, byte[] plaintextBytes)
{} // TODO
public byte[] Decrypt(byte[] keyBytes, byte[] ciphertextBytes)
{} // TODO
}
如您所见,.NET 的AesCryptoServiceProvider 是有状态的——它需要一个键作为属性。但据我了解,服务不应该是有状态的。
- 这里的主要问题是关键是属性(而不是方法参数)吗?
- 您将如何以 DDD 方式实现该类?
- 在某些情况下,使用给定密钥初始化提供程序似乎有用且高效(如果该密钥被大量使用)。有状态服务是否有理由或替代方案?
我想我们可以在每个方法调用上都实例化一个新的 Provider,但这似乎非常浪费。我们可以实施缓存来减少浪费,但整个事情开始感觉过度设计。
我想出的另一种选择是创建一个Aes256CbcCryptorFactory。工厂的CreateCryptor(byte[] key) 返回一个实际上是有状态的值对象Aes256CbcCryptor。如果需要进行多次加密调用,消费服务现在至少可以将此对象保留在其方法之一的范围内。
另一方面,这样的消费服务仍然不能将值对象存储在它的一个属性中,因为这样做会使该服务成为有状态的。
- 既然有一些好处,这件事已经完成了吗?对于值对象,这种行为类型似乎非常服务,但至少我们可以有一些临时状态。
【问题讨论】:
-
你用“穷人的 DI”评论了你的构造函数。这里没有 DI:您使用
new关键字来创建实例。也许这是你的问题。你不应该让AesCryptoServiceProvider成为一个依赖项,然后让你的组合根担心状态吗? -
@DavidOsborne 我省略了参数化构造函数,但现在添加了它。无参数构造函数是“穷人的 DI”,即框架自动注入的替代方案。不过,我对你的观点很感兴趣。组合根知道使用什么键是有道理的。该根将是服务本身,对吗?但是我们不需要防止 it(和 this)变成有状态的吗?
-
“穷人的 DI”是没有容器的 DI (blog.ploeh.dk/2014/06/10/pure-di/)。组合根实际上是什么取决于实现。您似乎无法逃避您选择的实现需要状态的事实。您可以进一步向上/向外推动该状态的解析,但在某些时候您需要管理该状态。
-
@DavidOsborne 同意。我正在寻找管理它的正确方法/地点,据我了解,这不在服务中。有什么建议吗?
-
@Timo 为什么你认为这与 DDD 有关?
标签: c# domain-driven-design stateful value-objects ddd-service