Java – where should the validation logic be?

topic

How do you best manage the construction of object graphs that require complex validation logic? I want to keep dependency injection, an undeniable reason for the testability of constructors

Testability is very important to me. How do you suggest maintaining this property of code?

background

I have a simple java object that manages the structure of some business data:

class Pojo
{
    protected final String p;

    public Pojo(String p) {
        this.p = p;
    }
}

I want to ensure that P is a valid format, because without this guarantee, this business object is really meaningless; If P is nonsense, it should not be created However, verification P is not trivial

grasp

Complex verification logic is really needed, and the logic should be completely testable, so I have a separate class of logic:

final class BusinessLogic() implements Validator<String>
{
    public String validate(String p) throws InvalidFoo,InvalidBar {
        ...
    }
}

Possible duplicate questions

> Where Should Validation Logic Be Implemented? – The accepted answer is impenetrable to me I read "run in the local environment of the class" as a tautology. How do validation rules run in anything other than the "local environment"? I didn't think of point 2, so I can't comment. > Where To Provide Logic Rules for Validation? – Both answers indicate that my customer / data provider's responsibilities are, in principle, what I like However, in my case, the client may not be the initiator of the data and cannot be verified. > Where To Keep Validation Logic? – The proposed validation can be owned by the model, but I find this method not suitable for testing Specifically, for each unit test, even if I'm testing other parts of the model, I need to care about all the validation logic - I can't completely isolate what I want to test by following the recommended methods

My idea so far

Although the following constructor publicly declares POJO's dependencies and retains its simple testability, it is completely unsafe There is nothing here to prevent the client from providing a verifier and claiming that every input is acceptable!

public Pojo(Validator businessLogic,String p) throws InvalidFoo,InvalidBar {
    this.p = businessLogic.validate(p);
}

Therefore, I limit the visibility of constructors. I provide a factory method to ensure validation, and then construct:

@VisibleForTesting
Pojo(String p) {
    this.p = p;
}

public static Pojo createPojo(String p) throws InvalidFoo,InvalidBar {
    Validator businessLogic = new BusinessLogic();
    businessLogic.validate(p);
    return new Pojo(p);
}

Now I can refactor createpojo into a factory class, which will restore POJO's "single responsibility" and simplify the testing of factory logic, let alone waste the performance advantages of creating new (stateless) businesslogic on each new POJO

My intuition is that I should stop and ask for outside opinions Am I on the right track?

Solution

Some of the following response elements

Introduction: I think your system can be a simple library, a multi-level application or a complex distributed system. In fact, there is not much difference when it comes to verification:

>Client: a remote client (such as an HTTP client) or just another class calls your library. > Service: a remote service (such as a rest Service) or your exposed API

Where to verify?

You typically want to verify the input parameters:

>On the client side, before passing parameters to the service, ensure that the early object will be valid This is especially necessary if it is a remote service or there is a complex process between generating parameters and creating objects. > On the server side:

>At the class level, in your constructor to ensure that you create valid objects; > At the subsystem level, the layer that manages these objects (for example, DAL persists your POJOs); > At the boundary of your service, such as the facade of your library or your controller or external API (such as MVC, such as rest endpoint, spring controller, etc.)

How to verify?

Assuming the above, because you may have to reuse your validation logic in multiple places, it may be a good idea to extract it in the utility class So:

>Don't copy it (dry!); You are sure that each layer of the system will be verified in the same way; > You can easily test this logic alone (because it is stateless)

More specifically, you will at least call this logic in the constructor to enforce the validity of the object (consider the effective dependency as a prerequisite for the algorithm in the Pojo method):

Practical Class:

public final class PojoValidator() {
    private PojoValidator() {
        // Pure utility class,do NOT instantiate.
    }

    public static String validateFoo(final String foo) {
        // Validate the provided foo here.
        // Validation logic can throw:
        // - checked exceptions if you can/want to recover from an invalid foo,// - unchecked exceptions if you consider these as runtime exceptions and can't really recover (less clutering of your API).
        return foo;
    }

    public static Bar validateBar(final Bar bar) {
        // Same as above...
        // Can itself call other validators.
        return bar;
    }
}

POJO class: please pay attention to the static import statement to improve readability

import static PojoValidator.validateFoo;
import static PojoValidator.validateBar;

class Pojo {
    private final String foo;
    private final Bar bar;

    public Pojo(final String foo,final Bar bar) {
        validateFoo(foo);
        validateBar(bar);
        this.foo = foo;
        this.bar = bar;
    }
}

How do I test my POJO?

>You should add and create unit tests, To ensure that the validation logic is called at build time (to avoid regression, because one can simplify the "constructor" later "by deleting this validation logic for X, y, Z reasons). > if they are simple, you can create dependencies inline because it makes your test easier to read because everything you use is local (less scrolling, smaller psychological scale, etc.) >However, if the setting of your POJO dependency is so complex / lengthy that the test is no longer readable, you can consider this setting in the @ before / setup method so that the logic of unit testing POJO really focuses on verifying your POJO behavior

Anyway, I agree with Jeff storey:

>Write the test with valid parameters, > the constructor without validation is just for your test When you mix production and test code, it is really a code smell and will certainly be used unintentionally by some people, including the stability of your service

Finally, think of your tests as code examples, examples, or executable specifications: you don't want to give confusing examples in the following ways:

>Inject invalid parameters; > Inject validators, which will confuse / read "strange" your API

What if POJO needs very complex dependencies?

[if this is the case, please let us know]

Production code: you can try to hide this complexity in the factory

Test code: or:

>As mentioned above, this is considered in different ways; Or use your factory; Either > use simulation parameters and configure them so that they verify your test pass requirements

Edit: several links on input validation for security, which can also be useful:

> OWASP’s Input Validation Cheat Sheet > OWASP’s wikipage about Data Validation

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>