-1

Following DDD practices, I am running into a problem while implementing a small AES encryptor/decryptor (wrapping .NET's 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
}

As you can see, .NET's AesCryptoServiceProvider is stateful - it takes a key as a property. But as I've come to understand, services are not supposed to be stateful.

  1. Is the key being a property (rather than, say, a method parameter) the main thing going wrong here?
  2. How would you implement the class in a DDD way?
  3. In some situations, having a provider initialized with a given key seems useful and efficient (if that key is to be used a lot). Is there a justification for stateful services, or an alternative?

I imagine we could just instantiate a new Provider on every method call, but it seems very wasteful. We could implement caching to reduce the waste, but the whole thing then starts feeling overengineered.

Another alternative I have come up with is to create an Aes256CbcCryptorFactory instead. The factory's CreateCryptor(byte[] key) returns a Value Object Aes256CbcCryptor that is actually stateful. A consuming service could now at least keep this object around in the scope of one of its methods, if it needs to make multiple cryptor calls.

On the other hand, such a consuming service could still not store the Value Object in one of its properties, as doing so would make that service stateful.

  1. Seeing as there are some benefits, is this a thing that is done? The type of behavior seems very servicey for a Value Object, but at least we can have some temporary state.
Timo
  • 7,992
  • 4
  • 49
  • 67
  • You've commented your constructor with 'Poor man's DI'. There's no DI here: you're using the the `new` keyword to create the instance. Maybe this is your problem. Shouldn't you make the `AesCryptoServiceProvider` a dependency and then let your composition root worry about the state? – David Osborne Oct 19 '16 at 09:46
  • @DavidOsborne I had omitted the parameterized constructor, but have added it now. The parameterless constructor is the "poor man's DI", i.e. an alternative to automatic injection by a framework. I am intrigued by your point, though. Makes sense that the composition root would know what key to use. That root would be a service itself, correct? But wouldn't we need to keep *it* (and this) from becoming stateful? – Timo Oct 19 '16 at 09:59
  • 'poor man's DI' is DI without a container (blog.ploeh.dk/2014/06/10/pure-di/). What the composition root actually is depends on the implementation. It would appear that you cannot escape the fact that your chosen implementation requires state. You can push the resolution of that state further and further upward/outward, but at some point you will need to manage that state. – David Osborne Oct 19 '16 at 10:25
  • @DavidOsborne Agreed. I am looking for the correct way/place to manage it, which to my understanding is not in a service. Any advice? – Timo Oct 19 '16 at 10:55
  • @Timo why do you think this is related to DDD? – guillaume31 Oct 19 '16 at 12:45
  • @guillaume31 Eric Evans wrote: "Make the SERVICE stateless." *That* is what is conflicting with my initial tendency to give this service state. I am looking to reconcile the two. – Timo Oct 21 '16 at 11:08
  • He is talking about infrastructure, application or domain services, not a tchnical utility like an encryptor. – guillaume31 Oct 21 '16 at 11:39
  • @guillaume31 Do you have a reference for that? But, say for the sake of argument that the encryptor (or rather, its interface) is a domain concern. Or think of another service entirely. – Timo Oct 24 '16 at 09:18
  • Do you have the page number for that quote? It is *absurd* to say for the sake of the argument that it's a regular service, because regular services don't need cryptographic keys to be initialized with. – guillaume31 Oct 24 '16 at 09:36
  • All in all, your question has nothing to do with DDD except that you stated your object was a "service" and Eric Evans, as a largely DDD-unrelated well-known advice, recommended that services be stateless. – guillaume31 Oct 24 '16 at 09:42
  • @guillaume31 Page 106, just above "Services and the Isolated Domain Layer". I am not sure why Evans' stateless service recommendation would be DDD-*un*related. But let's talk good practice, DDD or otherwise. I'd say for credit card recurring payments, both storage and encryption are definite domain requirements. (We must store the card number to do recurring payments later, as no customer will be present. Stored card numbers are required by PCI-DSS to be encrypted.) As such, we need a cryptographic key, and I am looking for where to ask for it and which component should hold it. – Timo Oct 24 '16 at 09:50
  • Even in that case, encryption is not your domain. Credit card recurring payment is your domain. Encryption is a technical auxiliary. – guillaume31 Oct 24 '16 at 10:02
  • Evans' stateless service recommendation is DDD-unrelated because everyone and their sisters make the same recommendation. – guillaume31 Oct 24 '16 at 10:03
  • Even if `Aes256CbcCryptor` were a service, are you sure it has state? Can you mutate it? – guillaume31 Oct 24 '16 at 10:05
  • I'd say the key is its state. A cryptor with key A is of no use to someone who needs to use key B. Do you consider it something other? Say I agree with you on encryption being outside the domain. What are the consequences of this? Does this provide an easier answer perhaps? If services are recommended to be stateless regardless of DDD... – Timo Oct 24 '16 at 10:59

1 Answers1

0

I would go with something like this:

public class Aes256CbcCryptor : ISymmetricCryptor
{
    private SymmetricAlgorithm AesProvider { get; }

    public Aes256CbcCryptor(Byte[] key)
    {
        // AesCryptoServiceProvider is not a 'volatile' dependency
        // therefore we don't need to inject it.
        this.AesProvider = 
            new AesCryptoServiceProvider()
            {
                ...
                Key = key// This is the real dependency, IMHO
            };
    }
}

Then the composition root, using 'poor man's DI' would like this:

public static sub Main()
{
    var key = SomeConfigSomewhere.GetSetting["key"];

    var cryptor = new Aes256CbcCryptor(key);

    var cipherText = cryptor.Encrypt("P@55w0rd");
}

Using a container would make resolution more neat but would essentially be the same:

public static sub Main()
{
    arbitraryContainer
        .Register
        .ServiceFor<ISymmetricCryptor>()
        .Using<Aes256CbcCryptor>()
        .DependingOn(SomeConfigSomewhere.GetSetting["key"]);

    var cryptor = arbitraryContainer.Resolve<ISymmetricCryptor>();

    var cipherText = cryptor.Encrypt("P@55w0rd");
}
David Osborne
  • 6,436
  • 1
  • 21
  • 35
  • I still see some issue as we move up the abstract layers. For this Cryptor, the key may make sense as a constructor dependency, as it is needed by virtually all of its methods. Now image there is a CardNumberCryptor service, which has the Cryptor as a dependency. It, in turn, is used by another service, which in turn is used by a RecurringCardPayment service. Now, I cannot instantiate/test RecurringCardPayment's methods without a key, which may only be relevant to *one* of its methods. – Timo Oct 24 '16 at 11:06
  • `CardNumberCryptor` should depend on `ISymmetricCryptor`. The underlying implementation is not its concern. – David Osborne Oct 24 '16 at 15:00
  • Ahh, and `CardNumberCryptor` is either passed some `ISymmetricCryptor`, or the container does this. With a container, I guess the container then needs to know what key to use, which means the container is tied very strongly to (in this example) our Main() method - as in your last example. Is this the normal state of affairs? It looks so... elaborate. – Timo Oct 24 '16 at 15:36
  • 1
    In this example, `Main()` is the composition root. This is where the container should be configured. When [direct] references to the container begin to leak out of the composition root, then you're using a `ServiceLocator` anti-pattern. I suppose it is a little elaborate but I suppose that's a potential cost of decoupling the components. – David Osborne Oct 24 '16 at 15:52
  • 1
    I would strongly recommend reading Mark Seemann's book (https://www.amazon.co.uk/dp/1935182501), if you can. While it's title is very focused, it covers so much ground regarding OO design and DI's role within it. – David Osborne Oct 24 '16 at 15:58