de

en

 

17.09.10 Vortrag Fachgruppe IT PM:Planung in IT Projekten

11.06.10 AG IT Architektur: Thema Generierung

07.06.10 Artikel: HTTPS zusammengefasst

 

Validierung mit JSR303 - aus Architektensicht

von M.Jerger

Das Problem: Redundanz

In dem Umfeld der Validierung besteht das Hauptproblem darin, dass Validierung an sehr vielen Stellen benötigt wird. Benötigt zwar auf ähnliche Art und Weise aber doch immer mit feinen Unterschieden.
Nehmen wir als Beispiel den Titel eines Objekts, das eine Seite in einem Content Management System (CMS) repräsentiert. Nehmen wir weiter an, dieser Titel muss eine Länge zwischen 1 und 25 besitzen. Jetzt wäre es schön, wenn wir

  • in einer Eingabemaske die Mindest- und Maximallänge anzeigen können (vielleicht als Tooltip?),
  • in einem Rich Client die Eingabe direkt validieren können (einen String auf seine Länge zu prüfen ist sicher schon vor dem Data-Binding und Datentypkonvertierung direkt möglich),
  • vor dem Speichern des Seitenobjekts die Validität des gesamten Objekts überprüfen können.
  • Vielleicht ist auch eine komplexere Überprüfung über die Objektgrenzen hinaus notwendig? Also wollen wir auch Aggregate validieren können.

Wieder zurück auf der abstrakten Ebene können wir Metadaten erkennen, mit denen wir unsere Domainobjekte feiner beschreiben können (z.B. die Min-/Maxlänge des Titels).
Und wir können die Stellen erkennen, an denen diese Metadaten verwendet werden (im obigen Beispiel als Tooltip, Eingabevalidierung, Objektvalidierung, ...).
Damit ist auch klar, das die Metadaten das sind, was über alle Ebenen wiederverwendet werden kann und die Stellen, an denen diese Metadaten verwendet werden enthalten die schon angedeuteten feinen Unterschiede.

Wo hilft der der JSR303?

Der JSR303 bietet sowohl in der Ecke der Metadaten als auch an den Stellen, an denen diese Metadaten verwendet werden alles notwendige - solange Sie nur Validierung im Sinn haben. Sobald Sie aber über Validierung hinausgehen und z.B. die Tooltips aus dem obigen Beispiel umsetzen wollen, werden Sie die unschönen Grenzen des JSR303 kennen lernen.
Aber zuerst einmal - was bietet Ihnen der JSR303:

Constraints für DomainObjekte zentral definieren

Feld-Constraints können direkt am Feld oder am Getter definiert werden.

  public class Page extends AbstractPage {
    @NotNull @Size(min=1,max=25)
    protected String title;
    ...

Objekt Constraints werdend entsprechend an der Klasse definiert.

Validierung durchführen

Validierung durchzuführen ist recht einfach. Mit

  Set validationResult = validator.validate(page);

können Sie das page Object aus dem obigen Beispiel validieren. Validierungsfehler werden soweit möglich zusammen als Set zurückgegeben. Wie Sie auf Validierungsfehler reagieren, kann sich hier nach den unterschiedlichen Umgebungsanforderungen richten.

Unterschiedliche Granularität

Für die unterschiedlichen Granularitäten (direkt nach der Eingabe, feldweit, objektweit, aggregatweit) bietet JSR303 zwei unterschiedliche Konzepte an. Zum einen bietet ein Validator unterschiedliche Methoden zur Validierung an:

validate() (wie oben verwendet) validiert das gesamte Objekt/Aggregat,
validateProperty() validiert nur ein Feld des DomainObjects und
validateValue() kann einen Wert validieren bevor er in das DomainObject gelangt.

Als zweites Konzept gibt es sogenannte Validierungsgruppen. Eine Validierungsgruppe erlaubt es Validierungen in einer weiteren Dimension noch feiner zu steuern. So macht es zum Beispiel Sinn, teure Validierungen (Rechenzeit, Speicherplatz) von billigen zu unterscheiden. So können die billigen Validierungen immer, die teuren Validierungen nur beim Speichern und ganz teure Validierungen vielleicht nur einmal nachts überprüft werden.

  public interface Cheap {}
  
  public class Page extends AbstractPage {
    @NotNull(groups=Cheap.class)
    protected String title;
    ...
    
  validator.validate(page, Cheap.class)

Wehrmutstropfen

Bis zu dieser Stelle ist alles in bester Ordnung. Nun kommen wir aber leider an eine unschöne Ecke. Constraint Annotations haben einen direkten Bezug zu ihrem verwendeten Validator. Damit existiert hier eine Abhängigkeit vom Abstrakten (Metadaten) zum Konkreten (Validator), was sehr bedauerlich ist.

  @Target({ElementType.METHOD, ElementType.FIELD})
  @Retention(RetentionPolicy.RUNTIME)
  @Constraint(validatedBy=OnlyAllowedCharacterValidator.class)
  public @interface OnlyAllowedCharacter {...}

Denn Sie können so niemals die eigentlich nützlichen Metadaten ohne Ihre Implementierung - die konkreten Validatoren - verwenden.

Das Validierungspunkt-Konzept mit AspectJ

Wenn Sie in Ihrem System solche Regeln wie "Factories erzeugen nur valide Objekte" oder "Das Repository speichert nur valide Objekte" verwenden, weiß jeder Entwickler in ihrem Team, wann z.B. ein NullCheck notwendig ist und wann nicht.
Damit schaffen Sie Validierungszonen. Objekte, die diese Validierungszonen betreten oder verlassen, müssen valide sein. Ihre Anwendung wird mit solchen Validierungszonen übersichtlicher und stabiler - allerdings ist es auch sehr mühsam und fehleranfällig, solche Regeln konsistent umzusetzen.
An dieser Stellen bieten sich Aspekte an - so ist es nicht mehr nötig, alle Factory-Methoden von Hand mit Validierungscode zu instrumentieren. Z.B. mit AspektJ können Sie das zuverlässig und vollständig automatisiert tun.

  public aspect ValidateableAspect {
	static Validator validator = AlmostCmsConfiguration.getInstance().validator();
	
	public boolean Validateable.isValid() {
		return validate().isEmpty();
	}

	public Set<ConstraintViolation<Validateable>> Validateable.validate() {
		Validateable instance = this;
		return validator.validate(instance);
	}
	
	pointcut validateableCreation() : 
		execution (Validateable+ Factory+.*(..));
	
	after() returning(Validateable v) : validateableCreation() {
		Set<ConstraintViolation<Validateable>> violations = v.validate();
		if(!violations.isEmpty()) {
			throw new ValidationException("Invalid Object: " + v + 
					":" + violations.toString());
		}
	}
  }

Das obige Beispiel erweitert alle Klassen, die das Interface Validateable implementieren per Intertypedeclaration um die Methoden isValid() und validate(). Der zweite Teil des Aspekts instrumentiert dann alle Methoden von Factories, sodass nicht valide Objekte (isValid()) eine Exception auslösen.

Abenteuer der ersten Schritte

Auch wenn es sich lohnt, Validierung nach JSR303 zu verwenden, sind die ersten Schritte alles andere, als vergnüglich oder einfach.
Bei mir persönlich war die erste schlechte Idee, dass ich auf Validierung mit Spring gesetzt habe ... Da Spring seine Abhängigkeiten zu 3rd Party Libs nicht dokumentiert, war hier langes Suchen und Ausprobieren angesagt. Als härtester Brocken erwies sich dabei das package javax.validation selbst. Sun bundelt JSR303 nämlich mit Java EE 6, d.h. Sie müssen glassfish installieren, um an die Interfaces von javax.validation zu kommen. Die Implementierung dafür stammt im Falle von Spring vom Hibernate-Team.
Nichts desto trotz - es lohnt sich ...
Um Ihnen einige Stolpersteine zu ersparen finden Sie den vollständigen Code unter der Website der AG IT Architektur Stuttgart.

Viel Spaß beim Ausprobieren.

 
 
 

Partner




SENS





Empfehlung




Michael Jerger
Zeppelinstr. 13 | 72770 Reutlingen
tel: 07121 / 578913
e-mail: jerger@jerger.org