Skip to content

Trip Offer Management System - Refactoring Documentation

Overview

The Trip Offer Management system has been refactored to follow the established TQPRO architectural patterns, providing proper separation of concerns and consistency with existing codebase standards.

Refactoring Summary

Before: Initial Implementation

  • Direct use of NTSEntity classes in API layer
  • Business logic embedded in API doXXX methods
  • No canonical entity layer
  • Direct EntityFacade usage without facade pattern

After: Refactored Implementation

  • Proper layering: API → Facade → Canonical Entities → Native Entities → Database
  • Business logic centralized in TripOfferFacade
  • Clean separation of concerns
  • Configuration-driven entity mapping

Architecture Layers

1. Database Layer

Location: tqapp/src/main/java/com/perun/tlinq/client/nts/db/trip/

Hibernate entity classes for PostgreSQL database tables:

  • TrippageEntity - Maps to nts.trip_page table
  • TripsnippetEntity - Maps to nts.trip_snippet table
  • TripskeletonEntity - Maps to nts.trip_skeleton table
  • TripsnippetfileEntity - Maps to nts.trip_snippet_file table

Features: - JPA annotations (@Entity, @Table, @Column) - Sequence-based ID generation - Validation constraints - Extends NTSEntity

2. Canonical Entity Layer

Location: tqapp/src/main/java/com/perun/tlinq/entity/trip/

Platform-independent entity classes:

  • CTripPage - Canonical trip page entity
  • CTripSnippet - Canonical trip snippet entity
  • CTripSkeleton - Canonical skeleton template entity
  • CTripSnippetFile - Canonical snippet file entity

Features: - Extend TlinqEntity - Implement Serializable - Plain Java objects (POJOs) - Business-focused field names - No database coupling

3. Facade Layer

Location: tqapp/src/main/java/com/perun/tlinq/entity/trip/TripOfferFacade.java

Business logic layer following the facade pattern:

Methods:

// Page Operations
List<CTripPage> listPages(Integer page, Integer pageSize)
CTripPage readPage(Integer pageId)
CTripPage writePage(CTripPage page)
void deletePage(Integer pageId)

// Snippet Operations
List<CTripSnippet> listSnippets(Integer pageId, Integer page, Integer pageSize)
List<CTripSnippet> getActiveSnippets(Integer pageId)
CTripSnippet readSnippet(Integer snippetId)
CTripSnippet writeSnippet(CTripSnippet snippet)
void deleteSnippet(Integer snippetId)

// Template Operations
List<CTripSkeleton> listSkeletons()
CTripSkeleton readSkeleton(Integer skeletonId)
List<CTripSnippetFile> listSnippetFiles()
CTripSnippetFile readSnippetFile(Integer snippetFileId)

// HTML Generation
Map<String, Object> generateHtmlPage(Integer pageId, Integer skeletonId, Integer snippetFileId)

Features: - Extends EntityFacade - Uses NTSServiceFactory - Validates input parameters - Handles business logic - Returns canonical entities - Session management

4. API Layer

Location: tqapi/src/main/java/com/perun/tlinq/api/TripOfferApi.java

REST API endpoints - thin layer delegating to facade:

Pattern:

@POST
@Path("/endpoint")
public Response endpoint(Map reqData) {
    try {
        session = ApiUtil.gmp(reqData, "session", String.class, false);
        ar = doEndpoint(reqData);
    } catch (TlinqClientException e) {
        ar = new TlinqApiResponse(e.getErrorCode(), e.getMessage());
    }
    return Response.ok(ar).build();
}

private TlinqApiResponse doEndpoint(Map reqData) throws TlinqClientException {
    String session = ApiUtil.gmp(reqData, "session", String.class, false);
    // Extract parameters
    TripOfferFacade facade = new TripOfferFacade(session);
    // Call facade method
    return new TlinqApiResponse(result);
}

Features: - Parameter extraction only - Exception handling - Logging - Response wrapping - No business logic

5. Configuration Layer

Location: config/tourlinq-config.xml

Entity mappings defining canonical ↔ native transformations:

<Entity name="TripPage"
        class="com.perun.tlinq.entity.trip.CTripPage"
        idField="pageId"
        defaultFactory="NTSServiceFactory">
    <EntityFactoryList>
        <Factory name="NTSServiceFactory"
                 nativeEntity="com.perun.tlinq.client.nts.db.trip.TrippageEntity">
            <ServiceList>
                <Service name="saveTripPage" action="create"/>
                <Service name="saveTripPage" action="update"/>
                <Service name="readTripPage" action="read"/>
                <Service name="readTripPage" action="search"/>
                <Service name="deleteTripPage" action="delete"/>
            </ServiceList>
            <FieldMappingList>
                <FieldMapping targetField="pageId" sourceField="pageId" mapping="DirectMapping"/>
                <FieldMapping targetField="pageName" sourceField="pageName" mapping="DirectMapping"/>
                <FieldMapping targetField="pageDesc" sourceField="pageDesc" mapping="DirectMapping"/>
            </FieldMappingList>
        </Factory>
    </EntityFactoryList>
</Entity>

Benefits of Refactoring

1. Separation of Concerns

  • API Layer: HTTP handling and parameter extraction
  • Facade Layer: Business logic and validation
  • Entity Layer: Data representation
  • Database Layer: Persistence

2. Maintainability

  • Business logic centralized in facade
  • Easy to locate and modify functionality
  • Clear responsibilities per layer

3. Testability

  • Facade can be unit tested independently
  • Mock facade in API tests
  • No HTTP dependencies in business logic

4. Consistency

  • Follows existing patterns (GroupManagerFacade, etc.)
  • Matches TQPRO architectural standards
  • Uses established conventions

5. Flexibility

  • Can swap database implementations
  • Can change API structure without affecting business logic
  • Configuration-driven mapping allows easy field changes

6. Reusability

  • Facade methods can be called from multiple APIs
  • Business logic not tied to REST endpoints
  • Canonical entities usable across modules

Migration Path

If you need to add a new field:

  1. Add to Database Entity:

    // TrippageEntity.java
    @Column(name = "new_field")
    private String newField;
    

  2. Add to Canonical Entity:

    // CTripPage.java
    private String newField;
    // + getter/setter
    

  3. Add Field Mapping:

    <!-- tourlinq-config.xml -->
    <FieldMapping targetField="newField" sourceField="newField" mapping="DirectMapping"/>
    

  4. Use in Facade (if needed):

    // TripOfferFacade.java
    // Access via canonical entity
    page.getNewField()
    

If you need to add a new operation:

  1. Add to Facade:

    public CTripPage doSomething(Integer id) throws TlinqClientException {
        // Business logic here
    }
    

  2. Add to API:

    @POST
    @Path("/page/something")
    public Response something(Map reqData) {
        // Standard API pattern
        ar = doSomething(reqData);
    }
    
    private TlinqApiResponse doSomething(Map reqData) {
        facade = new TripOfferFacade(session);
        result = facade.doSomething(id);
        return new TlinqApiResponse(result);
    }
    

Comparison with Existing Patterns

The refactored Trip Offer Management system follows the same patterns as:

GroupManagerFacade Pattern

  • Extends EntityFacade
  • Uses NTSServiceFactory
  • Search methods with SelectCriteriaList
  • Type-safe canonical entities

Entity Mapping Pattern

  • Similar to TripGroup, TripPax entities
  • DirectMapping for simple fields
  • Service definitions (create, update, read, search, delete)
  • Native entity specified in factory

API Pattern

  • Similar to GroupApi, VisaApi
  • doXXX methods for business logic delegation
  • Exception handling with TlinqApiResponse
  • Session management

Performance Considerations

  1. Entity Transformation: Minimal overhead due to configuration-driven mapping
  2. Facade Layer: Single additional method call, negligible impact
  3. Caching: Can be added at facade level without API changes
  4. Pagination: Implemented in facade for memory efficiency

Security

  • Session validation in facade constructor
  • Parameter validation before database access
  • TlinqClientException for controlled error messages
  • No SQL injection risk (using JPA/Hibernate)

Future Enhancements

Potential Additions:

  1. Caching Layer: Add Hazelcast caching in facade
  2. Audit Trail: Add entity change tracking
  3. Validation: Add JSR-303 bean validation
  4. Batch Operations: Add bulk insert/update methods
  5. Search Filters: Add advanced search criteria

Extension Points:

  • Add new factories in config (e.g., for different databases)
  • Add computed fields with custom mappings
  • Add entity lifecycle hooks
  • Add field-level security

Conclusion

The refactored Trip Offer Management system now properly adheres to TQPRO architectural patterns:

Layered Architecture: API → Facade → Entity → Database ✅ Separation of Concerns: Each layer has clear responsibility ✅ Configuration-Driven: Entity mapping in XML config ✅ Consistent Patterns: Matches existing codebase conventions ✅ Maintainable: Business logic centralized and testable ✅ Extensible: Easy to add fields, operations, or factories

The system maintains all original functionality while providing improved code organization, maintainability, and consistency with the established TQPRO architecture.

References

  • GroupManagerFacade: /tqapp/src/main/java/com/perun/tlinq/entity/group/GroupManagerFacade.java
  • TripGroup Entity Mapping: /config/tourlinq-config.xml (line 2709)
  • GroupApi: /tqapi/src/main/java/com/perun/tlinq/api/GroupApi.java
  • TQCOMMON Documentation: /docs/tqcommon/TQCOMMON_EXECUTIVE_SUMMARY.md