Backend Development

Learn how to build robust backend functionality for your Auto Commerce modules.

Service Layer

The service layer contains your module's core business logic.

Creating a Service

namespace Modules\YourModule\Services;

use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;

class YourModuleService
{
    protected $apiUrl;
    protected $apiKey;
    protected $tenant;

    public function __construct()
    {
        $this->tenant = tenancy()->tenant;
        $this->loadSettings();
    }

    protected function loadSettings()
    {
        $this->apiUrl = ModuleManager::getSetting('your-module', 'api_url', $this->tenant);
        $this->apiKey = ModuleManager::getSetting('your-module', 'api_key', $this->tenant);
    }

    public function syncData()
    {
        try {
            $data = $this->fetchFromApi();
            $this->processData($data);

            Log::info('Data synced successfully', [
                'module' => 'your-module',
                'tenant' => $this->tenant->id
            ]);

            return ['success' => true, 'count' => count($data)];
        } catch (\Exception $e) {
            Log::error('Sync failed', [
                'module' => 'your-module',
                'error' => $e->getMessage()
            ]);

            throw $e;
        }
    }

    protected function fetchFromApi()
    {
        $response = Http::withHeaders([
            'Authorization' => 'Bearer ' . $this->apiKey,
            'Accept' => 'application/json'
        ])
        ->timeout(30)
        ->get($this->apiUrl . '/endpoint');

        if (!$response->successful()) {
            throw new \Exception('API request failed: ' . $response->body());
        }

        return $response->json();
    }

    protected function processData($data)
    {
        foreach ($data as $item) {
            // Process each item
            $this->processItem($item);
        }
    }

    protected function processItem($item)
    {
        // Your processing logic
    }
}

Dependency Injection

Use Laravel's service container for dependencies:

namespace Modules\YourModule\Services;

use App\Core\Services\OrderService;
use App\Core\Services\CustomerService;

class YourModuleService
{
    protected $orderService;
    protected $customerService;

    public function __construct(
        OrderService $orderService,
        CustomerService $customerService
    ) {
        $this->orderService = $orderService;
        $this->customerService = $customerService;
    }

    public function createOrderFromExternal($externalData)
    {
        // Find or create customer
        $customer = $this->customerService->findOrCreateFromExternal($externalData['customer']);

        // Create order
        $order = $this->orderService->create([
            'customer_id' => $customer->id,
            'source_module' => 'your-module',
            'external_id' => $externalData['id'],
            'items' => $externalData['items'],
            'total' => $externalData['total']
        ]);

        return $order;
    }
}

Controllers

API Controllers

namespace Modules\YourModule\Http\Controllers\Api;

use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use App\Http\Controllers\Controller;
use Modules\YourModule\Services\YourModuleService;
use Modules\YourModule\Http\Requests\ConnectRequest;

class YourModuleController extends Controller
{
    protected $service;

    public function __construct(YourModuleService $service)
    {
        $this->service = $service;
    }

    /**
     * Connect to external service
     */
    public function connect(ConnectRequest $request): JsonResponse
    {
        $validated = $request->validated();

        // Save settings
        ModuleManager::setSetting('your-module', tenancy()->tenant, $validated);

        // Test connection
        $status = $this->service->testConnection();

        if (!$status['success']) {
            return response()->json([
                'message' => 'Connection failed: ' . $status['error']
            ], 400);
        }

        return response()->json([
            'message' => 'Connected successfully',
            'data' => $status
        ]);
    }

    /**
     * Sync data from external service
     */
    public function sync(Request $request): JsonResponse
    {
        $request->validate([
            'since' => 'nullable|date'
        ]);

        $result = $this->service->syncData($request->input('since'));

        return response()->json([
            'message' => 'Sync completed',
            'data' => $result
        ]);
    }

    /**
     * Get connection status
     */
    public function status(): JsonResponse
    {
        $isConnected = ModuleManager::isEnabled('your-module', tenancy()->tenant);
        $settings = ModuleManager::getSettings('your-module', tenancy()->tenant);

        return response()->json([
            'connected' => $isConnected,
            'settings' => $settings,
            'last_sync' => Cache::get('your-module.last_sync')
        ]);
    }
}

Form Requests

Validate incoming data:

namespace Modules\YourModule\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class ConnectRequest extends FormRequest
{
    public function authorize()
    {
        return $this->user()->can('manage-modules');
    }

    public function rules()
    {
        return [
            'api_url' => 'required|url',
            'api_key' => 'required|string|min:20',
            'api_secret' => 'required|string|min:20'
        ];
    }

    public function messages()
    {
        return [
            'api_key.min' => 'API key appears to be invalid. Please check your credentials.',
            'api_url.url' => 'Please provide a valid API URL.'
        ];
    }
}

Events & Listeners

Dispatching Events

namespace Modules\YourModule\Services;

use Modules\YourModule\Events\DataSynced;

class YourModuleService
{
    public function syncData()
    {
        $data = $this->fetchFromApi();
        $processed = $this->processData($data);

        // Dispatch event
        event(new DataSynced($processed));

        return $processed;
    }
}

Creating Events

namespace Modules\YourModule\Events;

use Illuminate\Queue\SerializesModels;

class DataSynced
{
    use SerializesModels;

    public $data;
    public $timestamp;

    public function __construct($data)
    {
        $this->data = $data;
        $this->timestamp = now();
    }
}

Creating Listeners

namespace Modules\YourModule\Listeners;

use Modules\YourModule\Events\DataSynced;
use Illuminate\Support\Facades\Log;

class LogDataSynced
{
    public function handle(DataSynced $event)
    {
        Log::info('Data synced from YourModule', [
            'count' => count($event->data),
            'timestamp' => $event->timestamp
        ]);
    }
}

Listening to Core Events

// In your ServiceProvider
use App\Core\Events\OrderCreated;
use Modules\YourModule\Listeners\HandleOrderCreated;

public function boot()
{
    Event::listen(OrderCreated::class, HandleOrderCreated::class);
}

// Listener
namespace Modules\YourModule\Listeners;

use App\Core\Events\OrderCreated;

class HandleOrderCreated
{
    public function handle(OrderCreated $event)
    {
        $order = $event->order;

        // Send order to your external service
        app(YourModuleService::class)->sendOrder($order);
    }
}

Queue Jobs

Creating Jobs

namespace Modules\YourModule\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Modules\YourModule\Services\YourModuleService;

class ProcessBulkSync implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public $timeout = 300; // 5 minutes
    public $tries = 3;

    protected $tenantId;
    protected $startDate;
    protected $endDate;

    public function __construct($tenantId, $startDate, $endDate)
    {
        $this->tenantId = $tenantId;
        $this->startDate = $startDate;
        $this->endDate = $endDate;
    }

    public function handle(YourModuleService $service)
    {
        // Initialize tenant context
        tenancy()->initialize($this->tenantId);

        // Process sync
        $service->syncDataRange($this->startDate, $this->endDate);

        // Cleanup
        tenancy()->end();
    }

    public function failed(\Throwable $exception)
    {
        // Handle failure
        Log::error('Bulk sync failed', [
            'tenant' => $this->tenantId,
            'error' => $exception->getMessage()
        ]);

        // Notify admin
        // NotificationService::notifyAdmin(...);
    }
}

Dispatching Jobs

// Dispatch immediately
ProcessBulkSync::dispatch($tenantId, $startDate, $endDate);

// Dispatch with delay
ProcessBulkSync::dispatch($tenantId, $startDate, $endDate)
    ->delay(now()->addMinutes(5));

// Dispatch to specific queue
ProcessBulkSync::dispatch($tenantId, $startDate, $endDate)
    ->onQueue('sync');

// Chain jobs
ProcessBulkSync::withChain([
    new NotifyAdminOfCompletion($tenantId),
    new CleanupTempData($tenantId)
])->dispatch($tenantId, $startDate, $endDate);

Database Operations

Query Builder

use Illuminate\Support\Facades\DB;

public function getRecentOrders($limit = 10)
{
    return DB::table('orders')
        ->where('source_module', 'your-module')
        ->where('created_at', '>=', now()->subDays(7))
        ->orderBy('created_at', 'desc')
        ->limit($limit)
        ->get();
}

Eloquent ORM

use Modules\YourModule\Entities\YourModel;

// Create
$model = YourModel::create([
    'tenant_id' => tenancy()->tenant->id,
    'field' => 'value'
]);

// Update
$model->update(['field' => 'new value']);

// Delete
$model->delete();

// Query
$models = YourModel::where('tenant_id', tenancy()->tenant->id)
    ->where('is_active', true)
    ->with('relation')
    ->get();

Transactions

use Illuminate\Support\Facades\DB;

public function complexOperation()
{
    DB::transaction(function () {
        // All or nothing
        $order = Order::create([...]);
        $shipment = Shipment::create([...]);
        $this->externalService->notify($order);
    });
}

Error Handling

Try-Catch

public function riskyOperation()
{
    try {
        $result = $this->externalApiCall();
        return $result;
    } catch (\GuzzleHttp\Exception\RequestException $e) {
        Log::error('API request failed', [
            'error' => $e->getMessage(),
            'response' => $e->getResponse()?->getBody()?->getContents()
        ]);

        throw new \Exception('Failed to communicate with external service');
    } catch (\Exception $e) {
        Log::error('Unexpected error', ['error' => $e->getMessage()]);
        throw $e;
    }
}

Custom Exceptions

namespace Modules\YourModule\Exceptions;

class ApiConnectionException extends \Exception
{
    public static function invalidCredentials()
    {
        return new static('Invalid API credentials provided');
    }

    public static function rateLimitExceeded()
    {
        return new static('API rate limit exceeded. Please try again later.');
    }
}

// Usage
if (!$this->isValidCredential()) {
    throw ApiConnectionException::invalidCredentials();
}

Testing

Unit Tests

namespace Modules\YourModule\Tests\Unit;

use Tests\TestCase;
use Modules\YourModule\Services\YourModuleService;

class YourModuleServiceTest extends TestCase
{
    public function test_can_fetch_data()
    {
        Http::fake([
            '*' => Http::response(['data' => 'test'], 200)
        ]);

        $service = app(YourModuleService::class);
        $result = $service->fetchData();

        $this->assertNotEmpty($result);
    }
}

Feature Tests

namespace Modules\YourModule\Tests\Feature;

use Tests\TestCase;

class YourModuleApiTest extends TestCase
{
    public function test_can_connect_to_service()
    {
        $response = $this->postJson('/api/integrations/yourmodule/connect', [
            'api_url' => 'https://api.example.com',
            'api_key' => 'test-key'
        ]);

        $response->assertStatus(200)
                ->assertJson(['message' => 'Connected successfully']);
    }
}

Next Steps