moqui-service-writer
schue/moqui-skillThis skill should be used when users need to create, validate, or modify Moqui framework services, entities, and queries. It provides comprehensive guidance for writing correct Moqui XML definitions, following framework patterns and conventions.
SKILL.md
name: moqui-service-writer description: This skill should be used when users need to create, validate, or modify Moqui framework services, entities, and queries. It provides comprehensive guidance for writing correct Moqui XML definitions, following framework patterns and conventions. license: Complete terms in LICENSE.txt
Moqui Service Writer
Overview
This skill enables writing correct Moqui framework services, entities, and queries by providing comprehensive patterns, validation tools, and reference documentation. It helps developers follow Moqui conventions and avoid common pitfalls when working with the enterprise framework.
Quick Start
For New Moqui Development
- Generate service templates using
scripts/generate_service.py --interactive - Create entity definitions using
assets/entity_template.xmlas a starting point - Validate XML files with
scripts/validate_service.pyandscripts/validate_entity.py - Format XML consistently using
scripts/format_moqui_xml.py
For Existing Moqui Projects
- Validate current files to identify issues and improvements
- Reference patterns in
references/for best practices - Use templates in
assets/for new components - Apply consistent formatting across all XML files
Writing Services
Service Creation Workflow
Step 1: Choose Service Type
Determine service purpose and select appropriate verb:
- create - Create new records
- update - Modify existing records
- find - Search/retrieve records
- get - Retrieve single record
- delete - Delete records
- process - Custom business logic
Step 2: Generate Template
Use service generator:
python3 scripts/generate_service.py --interactive
Or generate specific types:
python3 scripts/generate_service.py --verb create --noun Product --type create
Step 3: Customize Service
Edit the generated service following patterns in references/service_patterns.md:
- Add proper authentication levels
- Define input/output parameters
- Implement business logic in actions
- Add error handling and validation
Step 4: Validate Service
python3 scripts/validate_service.py path/to/your/service.xml
Common Service Patterns
CRUD Services
Use entity-auto services for standard operations:
<service verb="create" noun="Product" type="entity-auto">
<in-parameters>
<auto-parameters entity-name="com.example.Product" include="nonpk"/>
</in-parameters>
<out-parameters>
<parameter name="productId"/>
</out-parameters>
</service>
Search Services
Implement find services with filtering:
<service verb="find" noun="Product" allow-remote="true">
<in-parameters>
<parameter name="productTypeEnumId"/>
<parameter name="statusId"/>
<parameter name="orderByField" default-value="productName"/>
</in-parameters>
<out-parameters>
<parameter name="productList" type="List"/>
<parameter name="totalCount" type="Integer"/>
</out-parameters>
<actions>
<entity-find entity-name="com.example.Product" list="productList" count="totalCount">
<econdition field-name="productTypeEnumId" ignore-if-empty="true"/>
<econdition field-name="statusId" ignore-if-empty="true"/>
<order-by field-name="${orderByField}"/>
</entity-find>
</actions>
</service>
Custom Business Logic Services
Implement complex operations with proper error handling:
Follow Moqui Framework Transaction Patterns:
- Default: No transaction attribute (98.5% of framework services)
- Critical Operations: Use
transaction="force-new"for financial/bulk operations - Long-running: Add
transaction-timeoutfor operations > 5 minutes - Avoid:
transaction="cache"(causes locking issues in framework)
<!-- Standard service - use framework defaults -->
<service verb="process" noun="Order" authenticate="true">
<in-parameters>
<parameter name="orderId" type="id" required="true"/>
</in-parameters>
<actions>
<!-- Validate order exists -->
<entity-find-one entity-name="com.example.Order" value-field="order"/>
<if condition="order == null">
<return error="true" message="Order not found with ID: ${orderId}"/>
</if>
<!-- Check permissions -->
<set field="hasPermission" from="ec.user.hasPermission('ORDER', 'PROCESS')"/>
<if condition="!hasPermission">
<return error="true" message="Permission denied"/>
</if>
<!-- Business logic -->
<service-call name="update#OrderStatus" in-map="[orderId: orderId, statusId: 'OsProcessing']"/>
<service-call name="send#OrderNotification" in-map="[orderId: orderId]"/>
<message public="true" type="success">Order processed successfully</message>
</actions>
</service>
<!-- Critical financial operation - explicit transaction -->
<service verb="close" noun="FinancialPeriod" authenticate="true"
transaction="force-new" transaction-timeout="600">
<in-parameters>
<parameter name="financialPeriodId" type="id" required="true"/>
</in-parameters>
<actions>
<!-- Critical financial closing logic -->
<script>ec.transaction.commitBeginOnly();</script>
<!-- ... closing operations ... -->
</actions>
</service>
Transaction Management Best Practices
Framework Patterns (Based on Moqui Framework Analysis)
Default Behavior (98.5% of framework services):
<!-- Most services - no transaction attribute needed -->
<service verb="create" noun="Example" authenticate="true">
<!-- Framework handles transactions automatically -->
</service>
Critical Operations (1.5% of framework services):
<!-- Financial/bulk operations requiring isolation -->
<service verb="close" noun="FinancialPeriod" authenticate="true"
transaction="force-new" transaction-timeout="600">
<!-- Long-running critical operation -->
</service>
Transaction Attribute Guidelines
| Scenario | Transaction Attribute | Timeout | When to Use |
|---|---|---|---|
| Standard CRUD | (none) | (none) | 98.5% of services |
| Financial Operations | force-new |
600-3600s | Period closing, payments |
| Bulk Data Operations | force-new |
600-1800s | Import/export, cleanup |
| Long-running Tasks | (none) | 600+ | Add timeout only if >5min |
| Record Locking Issues | ignore |
(none) | Bulk operations only |
Anti-Patterns to Avoid
❌ Avoid transaction="cache"
- Framework comments indicate locking and stale data issues
- Being removed from order processing in framework
❌ Avoid transaction="use-or-begin"
- Redundant (this is the framework default)
- Adds noise to service definitions
❌ Avoid transaction-timeout="60"
- Too short for most operations
- Framework uses 600-3600 seconds for long operations
Recommended Approach
- Start with no transaction attribute (framework default)
- Add
transaction="force-new"only for critical financial/bulk operations - Add
transaction-timeoutonly for operations expected to run >5 minutes - Test thoroughly - transaction boundaries affect data consistency
Creating Entities
Entity Design Workflow
Step 1: Plan Entity Structure
Consider:
- Primary key design (usually
{entityName}Id) - Required fields and data types
- Relationships to other entities
- Audit trail requirements
- Status and type enumerations
Step 2: Use Entity Template
Start with assets/entity_template.xml and customize:
- Update entity name and package
- Define fields with appropriate types
- Add relationships with short aliases
- Include seed data for enumerations
Step 3: Validate Entity
python3 scripts/validate_entity.py path/to/your/entity.xml
Field Type Selection
Use references/field_types.md to choose appropriate types:
Common Patterns
<!-- Primary Key -->
<field name="productId" type="id" is-pk="true"/>
<!-- Text Fields -->
<field name="productName" type="text-medium" enable-localization="true"/>
<field name="description" type="text-long" enable-localization="true"/>
<!-- Numeric Fields -->
<field name="price" type="currency-amount"/>
<field name="quantity" type="number-integer"/>
<!-- Date Fields -->
<field name="createdDate" type="date-time"/>
<field name="fromDate" type="date-time"/>
<field name="thruDate" type="date-time"/>
<!-- Status Fields -->
<field name="statusId" type="id" enable-audit-log="true"/>
<!-- Audit Fields -->
<field name="createdByUserAccountId" type="id" enable-audit-log="true"/>
<field name="lastUpdatedStamp" type="date-time"/>
Relationship Design
One-to-Many
<!-- In parent entity -->
<relationship type="many" related="com.example.OrderItem" short-alias="items">
<key-map field-name="orderId"/></relationship>
<!-- In child entity -->
<relationship type="one" related="com.example.OrderHeader" short-alias="order">
<key-map field-name="orderId"/></relationship>
Many-to-One
<relationship type="one" related="moqui.basic.StatusItem" short-alias="status">
<key-map field-name="statusId"/></relationship>
Self-Referencing
<relationship type="one-nofk" title="Parent" related="com.example.Category" short-alias="parent">
<key-map field-name="parentCategoryId" related="categoryId"/></relationship>
Writing Queries
Entity-Find Patterns
Basic Queries
<!-- Find single record -->
<entity-find-one entity-name="com.example.Product" value-field="product"/>
<!-- Find multiple records -->
<entity-find entity-name="com.example.Product" list="productList">
<econdition field-name="statusId" value="PsActive"/>
<order-by field-name="productName"/>
</entity-find>
Filtered Queries
<entity-find entity-name="com.example.Product" list="productList">
<econdition field-name="productTypeEnumId" from="productTypeEnumId" ignore-if-empty="true"/>
<econdition field-name="statusId" from="statusId" ignore-if-empty="true"/>
<econdition field-name="productName" operator="like" value="${searchText}%"/>
<order-by field-name="productName"/>
</entity-find>
Date Range Queries
<entity-find entity-name="com.example.Order" list="orderList">
<date-filter valid-date="orderDate"/>
<order-by field-name="orderDate" descending="true"/>
</entity-find>
Pagination
<entity-find entity-name="com.example.Product" list="productList" count="totalCount">
<econdition field-name="statusId" value="PsActive"/>
<order-by field-name="productName"/>
</entity-find>
<!-- Manual pagination -->
<script>
def startIdx = pageIndex * pageSize
def endIdx = startIdx + pageSize
productList = productList.subList(startIdx, Math.min(endIdx, productList.size()))
</script>
Complex Query Patterns
Join Queries
<entity-find entity-name="com.example.Product" list="productList">
<econdition field-name="status.description" operator="like" value="${statusFilter}%"/>
<relationship-join relationship="status"/>
<order-by field-name="productName"/>
</entity-find>
View Entity Queries
<entity-find entity-name="com.example.ProductAndStatus" list="productList">
<econdition field-name="statusId" value="PsActive"/>
<econdition field-name="statusDescription" operator="like" value="${statusFilter}%"/>
<order-by field-name="productName"/>
</entity-find>
Security and Permissions
Authentication Levels
Choose appropriate authentication based on sensitivity:
anonymous-all- Public servicesanonymous-view- Read-only public datatrue- User must be logged in- Add
require-all-rolesfor restricted access
Permission Checking
<set field="hasPermission" from="ec.user.hasPermission('PRODUCT', 'CREATE')"/>
<if condition="!hasPermission">
<return error="true" message="Permission denied"/>
</if>
Row-Level Security
<if condition="!ec.user.isUserInRole('ADMIN')">
<script>
productList = productList.findAll { it.createdByUserAccountId == ec.user.userId }
</script>
</if>
Validation and Quality Assurance
Service Validation
# Validate single service file
python3 scripts/validate_service.py service/ExampleServices.xml
# Validate entire directory
python3 scripts/validate_service.py --directory service/
Entity Validation
# Validate single entity file
python3 scripts/validate_entity.py entity/ExampleEntities.xml
# Validate entire directory
python3 scripts/validate_entity.py --directory entity/
XML Formatting
# Format single file with backup
python3 scripts/format_moqui_xml.py service/ExampleServices.xml
# Format directory without backup
python3 scripts/format_moqui_xml.py --directory entity/ --no-backup
Resources
scripts/
Executable tools for validation and generation:
- validate_service.py - Validate service XML files for patterns and structure
- validate_entity.py - Validate entity XML files for patterns and conventions
- generate_service.py - Generate service templates for common patterns
- format_moqui_xml.py - Format and pretty-print Moqui XML files
references/
Comprehensive documentation for reference while working:
- service_patterns.md - Common service patterns and examples
- entity_patterns.md - Entity design patterns and relationships
- query_examples.md - Entity-find query patterns and examples
- security_patterns.md - Authentication, authorization, and security patterns
- field_types.md - Complete field type reference with usage examples
assets/
Template files for starting new components:
- service_template.xml - Empty service template with proper structure
- entity_template.xml - Complete entity template with common patterns
- component_template.xml - Component configuration template
Best Practices
Service Design
- Use descriptive verb-noun combinations following Moqui conventions
- Include proper authentication based on service sensitivity
- Validate input parameters before processing
- Return meaningful error messages for debugging
- Use entity-auto services for simple CRUD operations
- Implement permission checks for sensitive operations
- Use transactions - Follow framework defaults (98.5% use no attribute), force-new for critical operations
Entity Design
- Include audit fields on transactional entities
- Use appropriate field types for data
- Define relationships with meaningful short-aliases
- Use view entities for complex queries
- Add seed data for enumerations and reference data
- Follow naming conventions consistently
Query Design
- Use ignore-if-empty for optional filter conditions
- Add proper ordering for consistent results
- Use pagination for large result sets
- Select specific fields when possible for performance
- Use date-filter for time-sensitive data
Security
- Always authenticate sensitive services
- Use principle of least privilege for permissions
- Validate all input parameters
- Implement audit logging for important operations
- Use parameterized queries to prevent SQL injection
Entity Field Validation
Always Validate Field Names
Before using field names in code, verify against entity definitions:
# Find entity definition
find . -name "*.xml" -path "*/entity/*" | xargs grep -l "entity-name=\"YourEntity\""
# Check available fields
grep -A 5 -B 5 "<field name="fieldName"*/entity/YourEntity.xml
Common Field Name Confusions
| Intended Field | Correct Field | Entity | Context |
|---|---|---|---|
| trackingUrl | masterTrackingUrl | ShipmentRouteSegment | Overall shipment tracking |
| trackingUrl | trackingUrl | ShipmentPackageRouteSeg | Package-level tracking |
| trackingIdNumber | masterTrackingCode | ShipmentRouteSegment | Overall tracking number |
| trackingIdNumber | trackingCode | ShipmentPackageRouteSeg | Package tracking number |
| externalId | otherPartyOrderId | OrderPart | External system reference |
Field Validation Workflow
- Check entity XML for correct field names
- Use field mapping tables when similar fields exist
- Validate with entity-find before using in code
- Test field access in development environment
Service Behavior Understanding
Critical Service Distinctions
Services can have misleading names - always verify actual behavior:
| Service | What It Actually Does | When to Use |
|---|---|---|
| ship#OrderPart | Creates NEW shipment + packs items + marks shipped | No existing shipment |
| create#OrderPartShipment | Creates shipment record only | Need custom packing logic |
| ship#Shipment | Marks existing shipment as shipped | Shipment exists, needs status change |
| update#OrderStatus | Changes order status only | Status transition needed |
Service Verification Workflow
- Read service implementation in XML files
- Check service description and parameters
- Look for entity-auto vs custom implementations
- Test service behavior in development
- Document actual behavior for future reference
Common Service Usage Patterns
<!-- WRONG: Assumes ship#OrderPart updates existing shipment -->
<service-call name="ship#OrderPart" in-map="[orderId: orderId]"/>
<!-- CORRECT: Different approaches for existing vs new -->
<if condition="existingShipment">
<service-call name="ship#Shipment" in-map="[shipmentId: shipmentId]"/>
<else>
<service-call name="ship#OrderPart" in-map="[orderId: orderId]"/>
</if>
Groovy Best Practices in Moqui
Closure Scope Management
Groovy closures have different variable scope rules than traditional blocks:
Problem Pattern
// BROKEN: logger not accessible in closure
existingShipments.each { shipment ->
logger.info("Shipment: ${shipment.shipmentId}") // NullPointerException!
}
Solution Patterns
// CORRECT: Use traditional for loop for outer variable access
for (EntityValue shipment in existingShipments) {
logger.info("Shipment: ${shipment.shipmentId}") // Works!
}
// CORRECT: Explicit variable passing
existingShipments.each { shipment ->
def log = logger // Explicit reference
log.info("Shipment: ${shipment.shipmentId}")
}
Scope Rules
- .each { } closures have limited outer variable access
- Traditional for loops maintain full scope access
- Explicit variable passing works but is verbose
- Prefer for-in loops when accessing outer variables
Systematic Debugging Approach
Debugging Priority Order
- Syntax Errors First - Must compile before logic testing
- Entity Field Names - Validate against definitions
- Service Behavior - Verify what services actually do
- Logic Flow - Check business logic after basics work
- Performance Issues - Optimize after functionality works
Incremental Testing Workflow
# 1. Fix syntax errors
groovy -cp . service/YourService.groovy
# 2. Test basic functionality
# Run service with minimal parameters
# 3. Add complex logic incrementally
# Test each addition separately
# 4. Full integration testing
# Test complete workflow
Brace Balance Validation
# Quick brace check
open_count=$(grep -o '{' service/YourService.groovy | wc -l)
close_count=$(grep -o '}' service/YourService.groovy | wc -l)
echo "Open: $open_count, Close: $close_count"
# Should be equal for valid syntax
Refactoring Mindset
Before Writing Custom Logic
Always ask these questions first:
-
Is there a Moqui service that does this?
- Check existing services in mantle-* components
- Look for entity-auto services
- Prefer built-in over custom implementation
-
Am I duplicating existing functionality?
- Review service patterns in references/
- Check if custom logic is necessary
- Consider extending vs replacing
-
Is this getting too complex?
- More than 20 lines = consider refactoring
- Multiple nested conditions = simplify
- Custom entity creation = use services
Refactoring Example
// COMPLEX: Manual shipment creation (48 lines)
if (existingShipment) {
// Manual tracking updates
// Manual route segment creation
// Manual shipment status changes
} else {
// Manual shipment creation
// Manual package creation
// Manual status updates
}
// SIMPLE: Use Moqui services (15 lines)
if (existingShipment) {
ec.service.sync().name("ship#Shipment")(shipmentId: shipmentId)
} else {
ec.service.sync().name("ship#OrderPart")(orderId: orderId)
}
Common Pitfalls to Avoid
- Missing Dependencies - Always declare component dependencies in component.xml
- Incorrect Field Types - Use appropriate field types (id, text-medium, number-decimal, etc.)
- Missing Relationships - Define relationships for foreign keys with proper key-map
- No Authentication - Set proper authenticate attribute on services
- Hardcoded Values - Use enumerations instead of hardcoded strings
- SQL Injection - Use entity-find with econdition, not raw SQL
- Transaction Issues - Follow framework patterns: default (no attribute), force-new for critical operations, timeout for long-running tasks
- Missing Localization - Use enable-localization for user-facing text
- Missing Audit Fields - Include createdDate, createdByUserAccountId, lastUpdatedStamp
- Incorrect Service Names - Follow verb-noun convention
- Invalid Field Names - Always validate against entity definitions
- Service Misunderstanding - Verify what services actually do before using
- Closure Scope Issues - Use traditional loops for outer variable access
- Over-engineering - Prefer existing Moqui services over custom logic
Troubleshooting
Validation Errors
- XML Parse Errors - Check for malformed XML, missing closing tags
- Missing Attributes - Add required attributes like entity-name, package
- Naming Issues - Follow Moqui naming conventions
- Type Errors - Use valid field types from reference
Service Issues
- Permission Denied - Check authentication and role requirements
- Parameter Errors - Verify parameter names and types
- Transaction Failures - Check transaction attributes and error handling
Entity Problems
- Relationship Errors - Verify key-map fields and related entities
- Missing Primary Keys - Ensure at least one is-pk field
- Seed Data Issues - Check enumeration and status item definitions