Programming pattern / architectural question
Asked Answered
S

7

4

I am currently working on a project where I have a BankAccount entity for some other entity.

Each bank account is a reference to a bank entity, an account number, and optionally an IBAN.

Now since an IBAN can be validated, how can I ensure that when the IBAN is set for an account is valid? What would be a clean architectural approach? I currently have a domain layer without any reference to any other layer and I like this clean approach (I was inspired by Eric Evans DDD). Fortunately, the IBAN validation can be performed without accessing any outside system so in this case I could have something like

public class BankAccount
{
  public string Iban
  {
     set { // validation logic here }
  }
}

But now I was thinking what approach I would use if the IBAN validation requires an SQL server check for example, or an external dll. How would I implement that? Would I create an IBAN value object that is passed to a service, that decides whether the IBAN is valid or not and after that set it to the BankAccount entity? Or would I create a factory that is allowed to instantiate IBANs and perform validation before?

Thanks for your help!

Spyglass answered 5/1, 2010 at 14:7 Comment(1)
I think you answered your own question in the end. The SPECIFICATION pattern in Evans Domain Driven Design pops to mind.Glantz
F
5

I would use some form of Inversion Of Control.

To be specific, I would have an interface called IIBANValidator. The various means of validating the IBAN should implement that interface. For example:

interface IBANValidator {
    Boolean Validate(string iban);
}

class SqlBanValidator : IBANValidator {

    public bool Validate(string iban) {
        // make the sql call to validate..
        throw new NotImplementedException();
    }

}

Then, I would have a method in my BankAccount class which accepted an object that implements IIBANValidator and the IBAN number and was structured like (not optimized by any stretch):

Boolean SetIBAN(IIBANValidator validator, String iban) {
  Boolean result = false;
  if (validator.Validate(iban)) {
    Iban = iban;
    result = true;
  }

  return result;
}

At this point your BankAccount class would not have to have a dependency on your validators, you could swap them out at will, and ultimately it's very clean.

The final code would look something like:

BankAccount account = new BankAccount();
account.SetIBAN(new SqlBanValidator(), "my iban code");

Obviously at runtime you could pass any validator instance you wanted.

Frick answered 5/1, 2010 at 14:18 Comment(1)
I would have a null check on the validator in case there isn't one available.Rowe
E
2

Rather than the IBAN number being a simple string, what if it was an actual class? You could implement validation in the constructor (if validation has no external dependencies), or you could use a factory to provide IBAN instances (if you need external validation). The important thing is that, if you have an IBAN instance, you know that it's a valid IBAN number.

Should BankAccount actually have a mutable IBAN number? I'm not terribly familiar with banking, but that sounds like a scary idea.

Ejaculatory answered 5/1, 2010 at 15:53 Comment(0)
T
1

You could implement a Specification that uses Dependency Injection for the Repository. You lose a bit of cohesion, though.

More details can be found here.

Tejada answered 5/1, 2010 at 14:21 Comment(0)
Q
0

Where to put validation logic depends on what information needed to perform that validation. Validation should be performed within type that has enough information to do that. On the other hand validation logic complexity must be taken into account as well. For example, if you have Email data attached only to Person type it can validated "in place" within person type because validation is not complex (assuming only email format is checked) and Person is the only consumer. If on the other hand you have deal data (characterized by goods data and price data) consumed by Store and Garage (selling your old stuff) types with validation logic that goods must belong to the Deal initiator it makes sense to put validation inside Deal type.

Quadrille answered 5/1, 2010 at 14:21 Comment(0)
E
0

You can just use a delegate to validate, you don't need to pass a whole interface, who ever wants to set it has to have a validator.

public delegate bool Validation(IBAN iban);
void SetIBAN(IBAN iban, Validation isValid){ 
  if(!isValid(iban)) throw new ArgumentException();
...}
Emanation answered 7/1, 2010 at 13:34 Comment(0)
B
0

I'd make it aspect oriented and reduce coupling.

    [IBANVlidator(Message = "your error message. can also come from culture based resouce file.")]
    public string IBAN
    {
        get
        {
            return _iban;
        }
        set
        {
            this.validate();
            _iban = value;
        }
    }

this.validate() is called from the base class of your BankAccount which iterates through all properties having validation attribute. validation atributes are custom attributes derived from ValidationAttribute class which can target class properties.

IBAN validation responsibility then is given to IBANValidator validation-attribute. of course this design can be improved which is beyond the scope of this answer.

Benumb answered 7/1, 2010 at 13:44 Comment(0)
R
0

Let me decompose your concerns and address them one by one.

Now since an IBAN can be validated, how can I ensure that when the IBAN is set for an account is valid? What would be a clean architectural approach?

I would use api-first approach - OpenAPI (formerly Swagger) YAML file defining a BankAccount entity with Jakarta validation (in a dedicated app module and use Gradle or Maven plugin to generate objects and server-side code - controllers with request and response objects, tests, etc. This keeps your implementation clean by separating concerns.

paths:
  /bank-accounts:
    post:
      summary: Create a Bank Account
      requestBody:
        required: true
components:
  schemas:
    BankAccount:
      type: object
      properties:
        bank:
          type: string
        accountNumber:
          type: string
        iban:
          type: string
      required:
        - bank
        - accountNumber
        - iban
      # Jakarta Validation constraints
      allOf:
        - $ref: '#/components/schemas/BankAccountValidation'
    BankAccountValidation:
      type: object
      properties:
        iban:
          type: string
          pattern: '^DE\d{2}\d{20}$'
          description: Must be a valid German IBAN
      required:
        - iban

For the 2nd part - DB/DLL/external validation, either use a validation service, or as you mentioned a Factory.

public class IbanValidationServiceImpl implements IbanValidationService {
  public boolean isValidIban(String iban) {
    if (iban.length() != 10) {
      return false;
    }

    return true;
  }
}

@Data
public class BankAccount {
  private String bank;
  private String accountNumber;
  private String iban;
}

@AllArgsConstructor
public class BankAccountValidator {
  private final IbanValidationService ibanValidationService;

  public boolean isValidBankAccount(BankAccount bankAccount) {
    return ibanValidationService.isValidIban(bankAccount.getIban());
  }
}

public interface IbanValidationService {
  boolean isValidIban(String iban);
}

BankAccountFactory using CDI:

public class BankAccountFactory {

  @Inject
  private IbanValidationService ibanValidationService;

  public BankAccount createBankAccount(String bank, String accountNumber, String iban) {
    if (ibanValidationService.isValidIban(iban)) {
      return new BankAccount(bank, accountNumber, iban);
    } else {
      throw new IllegalArgumentException("Invalid IBAN");
    }
  }
}
Rubel answered 21/4 at 13:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.