I'm working on some software where certain string identifiers used in string messages refer to either a production address or a test address. There is a formulaic way to convert a production identifier into a test identifier.
However, there is only one set of hardware–not separate systems for production and test.
During the initial development phase, we would like for the identifiers to be safely converted to test identifiers so no actual production messages accidentally get sent. Now, it does not seem right to me for the message generation class to know about test vs. prod--it should be "stupid" about this because test vs. prod is a business level concern. However, the message generation project contains the Identifier class as it is responsible for accepting such Identifiers and serializing them.
How can I keep the definition of an Identifier within the message generation project, but ensure that all identifiers actually used therein are converted to their test equivalents, without throwing procedural/conditional statements all over the place in the business layer?
In the message generation project, here's some sample code for an Identifier.
public sealed class Identifier {
private readonly string _identifier;
public Identifier(string identifier) {
_identifier = identifier;
}
public IdentifierCode { get { return _identifier; } }
public bool IsTest { get { return _identifier.BeginsWith("X"); } }
public Identifier ToTestIdentifier() {
return new Identifier("X" + _identifier.Substring(1);
}
}
In the business layer, here's a sample of how Identifiers might be constructed and used.
...
Message1234 message = new MessageBuilder { // from the generation layer
Sender = new Identifier(messageDetails.senderIdentifierCode),
Recipient = new Identifier(messageDetails.recipientIdentifierCode),
MessageDate = DateTime.Parse(messageDetails.date)
}.Build<Message1234>;
What kind of design pattern can I use that will make sure that, according to a project-level setting (perhaps even on disk or otherwise not committed in code or at the very least, committed in only ONE place), the Identifier properties created above will always have ToTestIdentifier run on them?
And for a bonus, how can I allow the construction of all test identifiers in a certain context to be dependent on a setting or conditional logic, so that (for example) if the recipientIdentifierCode is AARDVARK (as that recipient is known to be in test state), all Identifiers used in message generation become test identifiers, but if the recipientIdentifierCode is GORILLA (as that recipient is known to be in production state), all identifiers are left as-is, whether they are production or test?
To give more clarity, I'm trying to avoid this:
Identifier sender = new Identifier(messageDetails.senderIdentifierCode);
Identifier recipient = new Identifier(messageDetails.recipientIdentifierCode);
Message1234 message = new MessageBuilder {]
Sender = IsInTestMode(messageDetails.recipientIdentifierCode)
? sender.ToTestIdentifier()
: sender,
Recipient = IsInTestMode(messageDetails.recipientIdentifierCode)
? recipient.ToTestIdentifier()
: recipient,
MessageDate = DateTime.Parse(messageDetails.date)
}.Build<Message1234>;
This is fragile because it depends on the developer remembering, every single place an Identifier is used, to make it contextually test or not according to the proper rules (rules which could change). It also clutters the code–mixing into the algorithm for building messages the unrelated concern about the test/prod state of business entities. I'd like to dependency inject, or decorate, or provide a visitor, or something that can enable me to do it more like this, somewhere in the call chain, just once per message request:
// Hit the database or do some procedural logic to figure out if test mode is required.
bool shouldBeTestMode = GetTestModeFor(messageDetails.recipientIdentifierCode);
// activate some layer or create some object that wraps/translates/informs
// the rest of the system
MessageGenerationContext = new MessageGenerationContext(shouldBeTestMode);
GoBuildMessages(MessageGenerationContext);
// Now the magic happens somehow so that `new Identifier()` will always return a
// test identifier without any more code going through conditional contortions
// to make sure the proper kind of identifiers are used.
Oh, and to tie in my question title, I would like to make it hard or impossible for random code in the business layer to create an Identifier without the special logic being performed that determines whether it needs to be a test one or not, and converting it. Only a specific privileged place in the consumer of the message layer should be able to get to the Identifier constructor. All other new Identifier() code (or other means of creating identifiers) should be properly buffered so it can't build any test-context-insensitive Identifiers.
Aucun commentaire:
Enregistrer un commentaire