Importer Implementation Guide
Overview
The Inly Core framework provides a robust synchronization architecture for importing and exporting data between external services and your internal domain models. The architecture follows clear patterns for Extract-Transform-Load (ETL) operations, maintains separation between domain and external object models, and provides comprehensive logging through Traceflow. This document uses BitsData/Snowflake and HubSpot as examples of external service integrations, but the patterns apply to any external API or data source you want to synchronize with.Architecture Components
The Inly Core importer architecture follows a hierarchical class-based approach with clear separation of concerns:1. Extract-Transform-Load (ETL) Pattern
The system implements a clear ETL pattern with distinct responsibilities:Extract Phase
- External Services: Service classes use REST/GraphQL APIs to fetch data from external systems
- Data Sources: Each service handles authentication, pagination, and API-specific concerns
- Individual Importers: Each resource type has its own importer class
- Examples:
BusinessCentralservice withItemImporter,ColorImporter;HubSpotservice withContactImporter,DealImporter
Transform Phase
- Resource Importers: Each importer handles transformation for its specific resource type
- HasResourceObject Trait: Provides ETL methods for resource models (fillTransformedData, saveTransformedData)
- Property Mapping: Map external field names to internal field names within each importer
- Batch Timestamping: Track when data was imported and from which batch
Load Phase
- Upsert Operations: Individual importers handle inserting or updating records for their resource type
- Batch Processing: Support for bulk operations with timestamps via
ResourceImporterbase class - Relationship Management: Handle complex relationships between entities within specific importers
2. Model Structure
The architecture maintains a clear separation between different types of models:Domain Models (app/Models/)
Domain models represent the internal business entities:
- Use internal field names and business logic
- Contain computed properties and relationships
- May have external service integration traits
- Single source of truth for business data
External Object Models (app/Models/)
External object models store raw data from external systems. They should be prefixed with the service name (e.g., HubSpotContact, BusinessCentralItem) and implement ResourceObjectContract using the HasResourceObject trait.
- Implement
ResourceObjectContractfor display and ETL capabilities - Use
HasResourceObjecttrait for complete implementation - Store raw JSON data in
datafield (provided by trait) - Track batch import timestamps (
created_from_batch,updated_from_batch) - Track source timestamps (
source_created_at,source_updated_at) - Use external system’s primary keys with
HasStringKeytrait - Preserve original data structure for debugging
- Can have URLs pointing to external systems (e.g., “view in HubSpot”)
- No activity logging (only domain objects have that)
3. Importer Architecture
The new importer architecture uses a three-layer approach:Layer 1: ResourceImporter (Abstract Base)
The foundation class that provides common functionality for all importers:Layer 2: Service-Specific Base Importers
Each external service has its own base importer that extendsResourceImporter:
Layer 3: Individual Resource Importers
Each resource type has its own dedicated importer class:4. Service Classes
Service classes handle the low-level communication with external systems. These are implemented using the Saloon SDK pattern as documented in the Saloon SDK Implementation Guide. Key responsibilities:- Managing connections and authentication
- Handling API calls or database queries
- Managing pagination and rate limiting
- Providing consistent interfaces for data retrieval
BusinessCentral, HubSpot, Harvest, Notion services
For detailed implementation of service classes, refer to the Saloon SDK Implementation Guide.
5. Command Implementation
Master Import Command
The framework provides a master command pattern atstubs/laravel/app/Console/Commands/ImportResourceCommand.php:
Service-Specific Import Commands
The new architecture uses thegetAvailableImporters() method to dynamically discover importer classes:
Usage Examples
6. Workflow Integration
The new importer architecture integrates seamlessly with Laravel workflows for automated import processes:7. Traceflow Logging System
Traceflow provides unified logging across the entire sync process:- Contextual Logging: Automatically includes context information
- Multiple Targets: Database storage, console output, monitoring systems
- Structured Data: Supports additional context data
- Error Tracking: Automatic exception logging with stack traces
8. Implementation Guide for New Services
Follow these steps to implement a new external service importer using the new architecture:Step 1: Create Service Class
Create your service class following the Saloon SDK Implementation Guide. The service must provide methods for fetching resources that return pagination and data compatible with your resource classes.Step 2: Create External Object Models
Create models implementingResourceObjectContract with the HasResourceObject trait:
Step 3: Create Service-Specific Base Importer
Create a base importer class for your service. Environment can be null, or be used to support multiple instances of the same service. E.g. syncing orders from multiple Shopify instances.Step 4: Create Individual Resource Importers
Create specific importer classes for each resource type:Step 5: Create Import Command
Step 6: Create Migrations
Use theResourceImporterColumns helper for consistent column structure:
Step 7: Create Workflows (Optional)
Create workflow classes for orchestrated imports:Step 8: Configuration
Service configuration is handled by the service class itself as per the Saloon SDK Implementation Guide. The importer will use the service class to fetch data.Incremental Importing and Scheduling
Why Incremental Importing is Recommended
Incremental importing (using timestamps to fetch only recently updated data) is strongly recommended for production environments: Benefits:- Reduced API calls: Only fetch changed data since last sync
- Lower latency: Faster sync times for regular updates
- Better resource utilization: Less CPU, memory, and network usage
- Improved reliability: Smaller batches are less likely to timeout or fail
- Cost efficiency: Many APIs charge based on request volume
Scheduling Commands
Schedule both incremental and full imports in yourroutes/console.php. Fortnox is an example.
Scheduling Best Practices
- Staggered Timing: Schedule different resource types at different times to avoid API rate limits
- Incremental Frequency: Run incremental imports frequently (every hour or few hours)
- Full Sync Frequency: Run full imports daily or weekly as a safety net
- Overlap Prevention: Always use
withoutOverlapping()to prevent concurrent imports - Silent Mode: Use
--silentflag for scheduled tasks to reduce log noise
Triggering Imports by ID
For scenarios where you need to import specific records synchronously (e.g., webhooks, API endpoints, or manual triggers), use the importer’sget() method.
Basic Usage
Structure Overview
The new importer architecture follows a clean three-layer hierarchy:- Auto-Discovery:
getAvailableImporters()automatically finds all importer classes in a directory - Separation of Concerns: Each resource type has its own dedicated importer class
- Service-Specific Logic: Base importers handle service-specific authentication and API patterns
- Common Infrastructure:
ResourceImporterprovides pagination, error handling, and logging - Model Integration:
HasResourceObjecttrait provides ETL methods and display capabilities for resource models - Workflow Support: Importers integrate seamlessly with Laravel workflows for orchestration
- Clear Contracts:
ResourceObjectContractdefines the interface for all resource/integration models