Constraint System Architecture¶
The MetaObjects constraint system provides comprehensive validation and structural integrity for metadata definitions through a sophisticated two-tier constraint architecture. This system enforces rules during metadata construction, ensuring data quality and structural consistency in real-time.
Core Design Principles¶
The constraint system is built on several key architectural principles that align with MetaObjects' READ-OPTIMIZED WITH CONTROLLED MUTABILITY pattern:
Real-Time Enforcement¶
Constraints are enforced during metadata construction, not as an afterthought validation step:
// Constraints checked during addChild() operations
MetaObject user = new MetaObject("User");
MetaField invalidField = new MetaField("invalid::name"); // Contains ::
user.addChild(invalidField); // ❌ ConstraintViolationException thrown here
Performance Optimized¶
The constraint system follows MetaObjects' performance characteristics:
- Loading Phase: Full constraint validation (100ms-1s one-time cost)
- Runtime Phase: No constraint overhead (metadata is immutable)
- Memory Efficient: Constraints loaded once, cached permanently
Extensible by Design¶
Constraints use a provider-based registration system that allows for clean extensibility:
// Custom constraints integrated seamlessly
public class BusinessRuleConstraint extends BaseConstraint {
// Custom business logic validation
}
Two-Tier Constraint Architecture¶
MetaObjects uses a sophisticated two-tier system that separates structural constraints from value constraints:
graph TB
subgraph "Constraint System"
A[Placement Constraints] --> C[ConstraintEnforcer]
B[Validation Constraints] --> C
end
subgraph "Placement Constraints"
D["'X CAN be placed under Y'"]
E[Parent-Child Relationships]
F[Structural Rules]
end
subgraph "Validation Constraints"
G["'X must have valid Y'"]
H[Value Validation]
I[Business Rules]
end
C --> J[MetaData.addChild()]
C --> K[Real-Time Enforcement]
style A fill:#e1f5fe
style B fill:#f3e5f5
style C fill:#e8f5e8
Tier 1: Placement Constraints¶
Purpose: Define "What can be placed where" in the metadata hierarchy.
Pattern: PlacementConstraint
determines structural relationships between parent and child metadata.
// Example: "String fields CAN have maxLength attributes"
PlacementConstraint maxLengthPlacement = PlacementConstraint.allowAttribute(
"field.string.maxLength",
"String fields can have maxLength attribute",
MetaField.TYPE_FIELD, // Parent type: field
StringField.SUBTYPE_STRING, // Parent subtype: string
IntAttribute.SUBTYPE_INT, // Attribute type: int
StringField.ATTR_MAX_LENGTH // Attribute name: maxLength
);
Tier 2: Validation Constraints¶
Purpose: Define "What values are valid" for metadata properties.
Pattern: BaseConstraint
subclasses validate actual values against business rules.
// Example: "Field names must follow identifier pattern"
RegexConstraint fieldNaming = new RegexConstraint(
"field.naming.pattern",
"Field names must follow identifier pattern",
MetaField.TYPE_FIELD, // Target: field
"*", // Any subtype
"*", // Any name
"^[a-zA-Z][a-zA-Z0-9_]*$" // Regex pattern
);
Constraint Types¶
PlacementConstraint: Structural Rules¶
PlacementConstraint
uses pattern matching to define where metadata can be placed in the hierarchy:
Pattern Syntax¶
// Pattern components
"type.subtype[name]"
// Examples:
"field.string" // Any string field
"field.*" // Any field type
"attr.int[maxLength]" // Specific maxLength int attribute
"object.pojo" // Specific pojo object
"*" // Matches anything
Factory Methods for Common Patterns¶
// Allow attribute on specific field type
PlacementConstraint.allowAttribute(
"field.string.pattern",
"String fields can have pattern attribute",
MetaField.TYPE_FIELD,
StringField.SUBTYPE_STRING,
StringAttribute.SUBTYPE_STRING,
StringField.ATTR_PATTERN
);
// Allow attribute on any field type
PlacementConstraint.allowAttributeOnAnyField(
"field.any.required",
"Any field can be required",
BooleanAttribute.SUBTYPE_BOOLEAN,
MetaField.ATTR_REQUIRED
);
// Allow child type under parent
PlacementConstraint.allowChildType(
"object.contains.fields",
"Objects can contain fields",
MetaObject.TYPE_OBJECT, "*",
MetaField.TYPE_FIELD, "*"
);
Enforcement Logic¶
PlacementConstraints use an open policy - if any constraint allows the placement, it's permitted:
// Open Policy Example
List<PlacementConstraint> applicable = findApplicableConstraints(parent, child);
boolean allowed = false;
for (PlacementConstraint constraint : applicable) {
if (constraint.isAllowed()) {
allowed = true; // Any allowing constraint permits the placement
break;
}
}
ValidationConstraint: Value Rules¶
Validation constraints inherit from BaseConstraint
and validate actual values:
Built-in Validation Types¶
RequiredConstraint:
RequiredConstraint fieldNameRequired = new RequiredConstraint(
"field.name.required",
"Field names are required",
MetaField.TYPE_FIELD, "*", "name"
);
// Validates that field.name is not null or empty
RegexConstraint:
RegexConstraint identifierPattern = new RegexConstraint(
"identifier.pattern",
"Names must be valid identifiers",
"*", "*", "name",
"^[a-zA-Z][a-zA-Z0-9_]*$"
);
// Validates values against regex pattern
LengthConstraint:
LengthConstraint nameLength = new LengthConstraint(
"name.length",
"Names must be 1-64 characters",
"*", "*", "name",
1, 64 // min, max length
);
EnumConstraint:
EnumConstraint validTypes = new EnumConstraint(
"field.type.values",
"Field types must be from allowed set",
MetaField.TYPE_FIELD, "*", "type",
Arrays.asList("string", "int", "long", "double", "boolean", "date")
);
Target Pattern Matching¶
Validation constraints use declarative targeting instead of functional predicates:
public abstract class BaseConstraint implements Constraint {
protected final String targetType; // "field", "object", "attr", "*"
protected final String targetSubType; // "string", "int", "*"
protected final String targetName; // "maxLength", "required", "*"
public boolean appliesTo(MetaData metaData) {
return matchesPattern(metaData.getType(), targetType) &&
matchesPattern(metaData.getSubType(), targetSubType) &&
matchesPattern(metaData.getName(), targetName);
}
}
Unified Enforcement Architecture¶
The ConstraintEnforcer
provides a unified enforcement mechanism that processes both constraint types in a single pass:
Single Enforcement Loop¶
public void enforceConstraintsOnAddChild(MetaData parent, MetaData child) {
// UNIFIED: Single loop through all constraints
List<Constraint> allConstraints = metaDataRegistry.getAllValidationConstraints();
// Process placement constraints first
for (Constraint constraint : allConstraints) {
if (constraint instanceof PlacementConstraint) {
PlacementConstraint pc = (PlacementConstraint) constraint;
if (pc.appliesTo(parent, child)) {
// Apply placement logic
}
}
}
// Process validation constraints
for (Constraint constraint : allConstraints) {
if (constraint instanceof BaseConstraint) {
BaseConstraint vc = (BaseConstraint) constraint;
if (vc.appliesTo(child)) {
vc.validate(child, child.getName());
}
}
}
}
Performance Benefits¶
The unified approach provides significant performance improvements:
- 3x fewer constraint checking calls per operation
- No duplicate constraint processing
- Single cache lookup instead of multiple storage checks
- Elimination of empty list iterations
Constraint Registration¶
Constraints are registered through the integrated MetaDataRegistry system:
Programmatic Registration¶
public class CoreConstraintsProvider implements MetaDataTypeProvider {
@Override
public void registerTypes(MetaDataRegistry registry) {
// Register placement constraints
registry.addValidationConstraint(
PlacementConstraint.allowAttributeOnAnyField(
"field.required",
"Any field can be required",
BooleanAttribute.SUBTYPE_BOOLEAN,
MetaField.ATTR_REQUIRED
)
);
// Register validation constraints
registry.addValidationConstraint(
new RegexConstraint(
"field.naming.pattern",
"Field names must be identifiers",
MetaField.TYPE_FIELD, "*", "name",
"^[a-zA-Z][a-zA-Z0-9_]*$"
)
);
}
@Override
public int getPriority() {
return 0; // Core constraints loaded first
}
}
Service Discovery Integration¶
META-INF/services/com.metaobjects.registry.MetaDataTypeProvider:
com.metaobjects.core.CoreConstraintsProvider
com.metaobjects.database.DatabaseConstraintsProvider
Error Handling and Context¶
The constraint system provides rich error context for debugging and user feedback:
ConstraintViolationException¶
public class ConstraintViolationException extends MetaDataException {
private final String constraintId;
private final MetaData violatingMetaData;
private final Optional<String> suggestedFix;
public ConstraintViolationException(String message, String constraintId,
MetaData metaData) {
super(message, Optional.of(MetaDataPath.from(metaData)));
this.constraintId = constraintId;
this.violatingMetaData = metaData;
}
}
Rich Error Messages¶
// Example error message
"Field name 'invalid::name' violates constraint 'field.naming.pattern':
Field names must follow identifier pattern '^[a-zA-Z][a-zA-Z0-9_]*$'.
Consider using 'invalidName' instead."
Debugging Support¶
// Constraint checking can be disabled for testing
ConstraintEnforcer enforcer = ConstraintEnforcer.getInstance();
enforcer.disableConstraintChecking("test-context");
// Detailed logging available
log.debug("Enforcing {} constraints for adding [{}] to [{}]",
allConstraints.size(), child.toString(), parent.toString());
Schema Generation Integration¶
The constraint system is designed to integrate seamlessly with schema generators:
JSON Schema Integration¶
// Placement constraints become JSON Schema patterns
{
"properties": {
"fields": {
"type": "array",
"items": {
"properties": {
"maxLength": {
"type": "integer",
"minimum": 1
}
}
}
}
}
}
XSD Schema Integration¶
<!-- Validation constraints become XSD restrictions -->
<xs:simpleType name="identifierType">
<xs:restriction base="xs:string">
<xs:pattern value="^[a-zA-Z][a-zA-Z0-9_]*$"/>
<xs:minLength value="1"/>
<xs:maxLength value="64"/>
</xs:restriction>
</xs:simpleType>
Performance Characteristics¶
The constraint system maintains MetaObjects' performance expectations:
Constraint Loading¶
- One-time cost: Constraints loaded during registry initialization
- Memory resident: Constraints cached permanently like metadata
- Service discovery: Provider-based loading with priority ordering
Constraint Enforcement¶
- Loading phase only: Constraints only enforced during metadata construction
- Zero runtime cost: No constraint overhead during normal metadata access
- Thread-safe: No synchronization needed for constraint checking
Memory Usage¶
- Minimal overhead: ~1-5MB for typical constraint sets
- Efficient patterns: String patterns vs functional predicates
- Schema friendly: Constraints serializable to XSD/JSON Schema
Extensibility Guidelines¶
DO: Use Declarative Patterns¶
// ✅ GOOD - Declarative pattern matching
new RegexConstraint("email.format", "Must be valid email",
"field", "string", "email", "^[\\w._%+-]+@[\\w.-]+\\.[A-Za-z]{2,}$");
DON'T: Use Functional Predicates¶
// ❌ WRONG - Functional predicates not serializable to schemas
new CustomConstraint("email.format", "Must be valid email",
metadata -> metadata instanceof StringField && "email".equals(metadata.getName()),
value -> validateEmail(value));
DO: Extend BaseConstraint¶
// ✅ GOOD - Extend base constraint for validation rules
public class CreditCardConstraint extends BaseConstraint {
public void validate(MetaData metaData, Object value) {
// Custom validation logic
}
}
DO: Use Factory Methods¶
// ✅ GOOD - Use placement constraint factory methods
PlacementConstraint.allowAttributeOnAnyField(
"field.database.column",
"Any field can have database column mapping",
StringAttribute.SUBTYPE_STRING,
"dbColumn"
);
Next Steps¶
Now that you understand the constraint architecture, explore these areas:
-
Custom Constraints
Learn to create your own constraint types
-
Attribute System
Understand how attributes work with constraints
-
Type System
See how constraints integrate with type registration
-
Examples
Working examples of constraint usage
The constraint system provides the foundation for MetaObjects' data integrity and structural validation, ensuring that metadata definitions are correct, consistent, and comply with business rules from the moment they are created.