Egy programozó életében rendkívül gyakran előfordul, hogy entitásokat kell validálnia. Lényegében az üzleti logika mindig úgy kezdődik, hogy a bemeneti paramétereket, melyek legtöbbször felhasználói interakció által kerülnek be, validálni kell, hogy a hibás entitások a későbbiekben ne okozzanak hibát a program működésébe. A validálás az a dolog, amit egy kicsit mindenki másként csinál, mindenkinek megvan a saját módszere. Ebben az írásban bemutatnám, én hogyan is szoktam ezt a problémát orvosolni. A bemutatott megoldás nem tökéletes, de remélem gondolat-ébresztőnek megteszi.
A validálás alapproblémája szerintem a következő: az entitások validitását az üzleti réteg végzi el, amelyhez különböző kliensek kapcsolódnak. A különböző kliensek különböző nyelveken, és módon kívánják megjeleníteni a hibaüzeneteket, tehát az üzleti rétegbe írt return "Hibás a név!" és hasonlók megengedhetetlenek (később úgyis kiderül, hogy többnyelvű lesz a megjelenítés). A következő probléma, amivel szembesülünk, hogy egyszerre több hibát is tudni kell kezelni, hiszen egy bonyolult form kitöltésekor nem szerencsés egyesével untatni a felhasználót a hibák közlésével, meg egyébként sem tudjuk klienseink miként működnek. A nyelvi problémát orvosolni viszonylag egyszerű, vezessünk be jól dokumentált hibakódokat, klienseink, meg feldolgozzák saját belátásuk szerint. Az egyidejű több hibajelzés kérdésköre már egy kicsit rafináltabb dolog. A legegyszerűbb megoldás, egy hibakódokkal feltöltött tömb, vagy Collection visszaadása. Ezzel a megoldással a legnagyobb problémám, hogy rengeteg favágó programozással jár. Ha mindenhová, mind kliensben mind az üzleti rétegben kézzel írogatjuk a hibakódokat, akkor a hibaszázalékunk igencsak megnő, mert óhatatlanul elgépeljük valahol a kódot. Ha bevezetünk konstansokat, vagy Enumokat, akkor legalább a hibákat kiküszöböljük, de a ThisIsMyEntity.ERROR_NAME_REQ gépelésébe fogunk megőszülni.
Nekem a következő megoldás vált be: mivel 2db kettő hatványa sosem lesz egy harmadik kettő hatványával egyenlő, a hibakódot adjuk vissza kettő hatványaival. Tehát, ha hiányzik a név adjunk vissza 2-t, amennyiben a lakcím hiányzik adjunk vissza 4-et, ha pedig mindkettő hiányzik adjunk vissza 6-ot. A metódus implementációját személy szerint magába az Entitást megvalósító osztályba szoktam tenni, de ez ízlés dolga. Van aki szerint az Entitásnak csak egy DTO-nak kell lennie, és a validitás eldöntése az üzleti logika feladata. Véleményem szerint egy entitás tudja leginkább magáról eldönteni, hogy megfelelőek-e a benne foglalt adatok.
public int validate() {
int valid = 0;
if (foo == null || foo.isEmpty()) valid |= 1 << 1;
if (bar == null) valid |= 1 << 2;
if (param == null || param.isEmpty()) valid |= 1 << 3;
return valid;
}
Mivel későbbiekben is bitműveletekkel fogunk operálni, elegánsabbnak tartottam bitműveletekkel megoldani az összeadást, természetesen dolgozhatunk "hagyományos" számokkal is.Kliens oldalon a hibakód lekezelése pofon egyszerű. Kiválasztunk egy tetszőleges 2 hatványt (érdemes a dokumentációban is említettet választani :)), mondjuk a 4-et, és ellenőrizzük, hogy adott bit, 2^2 az 100, tehát a harmadik, egyezik-e a visszakapott érték harmadik bitjével.
boolean somethingIsValid = (4 & entity.validate()) == 4;Ez a módszer sem tökéletes, hiszen a hibákat csak bővíteni lehet, egyéb változtatás esetén már meglévő klienseink fognak hibásan működni. A teljes példaprogram az alábbi:
public class ValidatorTest {
private static final Map<Integer, String> ERRORS = new HashMap<Integer, String>();
static {
ERRORS.put(2, "Foo is required!");
ERRORS.put(4, "Bar is required!");
ERRORS.put(8, "Param is required!");
}
public static void main(String[] args) {
List<Entity> entities = Arrays.asList(new Entity[] {
new Entity(null, null, null),
new Entity("foo", null, null),
new Entity("", null, "param")
});
for(Entity entity : entities) {
System.out.println(entity);
int valid = entity.validate();
for(Integer code : ERRORS.keySet()) {
if ((code & valid) == code) {
System.out.println(ERRORS.get(code));
}
}
}
}
static class Entity {
private String foo;
private Integer bar;
private String param;
public Entity(String foo, Integer bar, String param) {
this.foo = foo;
this.bar = bar;
this.param = param;
}
public int validate() {
int valid = 0;
if (foo == null || foo.isEmpty()) valid |= 1 << 1;
if (bar == null) valid |= 1 << 2;
if (param == null || param.isEmpty()) valid |= 1 << 3;
return valid;
}
}
}
Nagyon kíváncsi vagyok a véleményetekre, illetve, hogy nektek milyen módszer vált be ebben a témakörbe.
Spring Validation (http://static.springsource.org/spring/docs/3.0.x/javadoc-api/org/springframework/validation/package-frame.html). Én jobban szeretem az entity-ktől külön létező validator-okat, mert sokszor belefutottam abba, hogy egy entity-t különböző képernyőkön különbözőképpen kell validálni, valahol létrejön, valahol csak ezt módosítják, valahol csak azt, másképp kell ellenőrizni szerepkörtől függően, stb. Ezért nem tetszik nekem pl. a JSR 303: Bean Validation sem, meg az még annotációkkal is meg van kavarva. Lehet, hogy ez most hitszegés, de nekem a validáció valahogy UI közeli dolog.
VálaszTörlésValamint a szöveges hibakódok tényleg elgépelhetők, viszont debug esetén könnyebben olvashatóak.
Valamint a FieldError osztály pl. tartalmaz paraméter listát is, így a hibaüzenetet megfelelőképpen paraméterezni lehet a szokásos {0} szintaxissal.
Egy időben játszottunk a Spring Modules-ban lévő Valang-gal is, mely azt ígérte, hogy majd JavaScript-et is tud majd gyártani, de valahogy visszatértünk arra, hogy az ellenőrzéseket is Java-ban implementáljuk, ne egy speciális kis nyelvben.
"a validáció valahogy UI közeli dolog" Ez a megjegyzés jó táptalaj egy kis eszmecseréhez :) Szerintem első szinten UI közelien is el kell végezni a validációt, ahol az adott felhasználó a saját jogosultságának, entity állapotának, stb. megfelelően beviszi az adatokat. Ez a validáció semmiképp sem helyettesítheti az üzleti rétegben történő ellenőrzést, hiszen nem bízhatunk a kliensekben. Ha az entitásnak több fázisa, életciklusa van, akkor az ellenőrzést fázisonként külön-külön meg kell valósítani.
VálaszTörlésNyilván, viszont a validációt a webservicek esetében is el kell végezni, söt, a leginkább ott, mert ott van lehetőség arra, hogy valami nagyon gonosz szivárogjon be a csendes kis magányunkba. Az pedig igen kellemetlen dolog.
VálaszTörlésÉn a hibaüzeneteket általában valamilyen frameworktöl kérem el, ami rögtön lokalizáltan adja vissza. Ez mondjuk akkor kellemetlen, ha a hívó API hívást használt, de lusta volt a HTTP headereket belőni a megfelelő nyelvre - de hát az meg vessen magára árpát. Azért az Accept-Language korrekt kezelése ma már minden keretrendszernél elvárható dolog szerintem.