Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Major Refactoring Project of the Backend to a Modern Framework Like Laravel(PHP) or Springboot(Java) #730

Open
ThomasAFink opened this issue Dec 10, 2024 · 8 comments
Labels
enhancement New feature or request php Pull requests that update Php code

Comments

@ThomasAFink
Copy link
Member

ThomasAFink commented Dec 10, 2024

Issue Description

The current backend structure is fragmented, making it difficult to navigate, maintain, and scale. Specifically, the backend is divided into the following components:

  1. zmsdb: Houses the database queries and migrations.
  2. zmsapi: Includes backend controllers.
  3. zmsentities: Contains backend object models.
  4. zmsclient: Use this library to fetch data from the eappointment zmsapi via HTTP.
  5. zmsslim: This module is intended to help with the creation of a Slim based framework application.

This separation introduces unnecessary complexity and detracts from the overall appeal of the system. I propose consolidating these components into a unified backend module using a robust framework such as Laravel or Spring Boot. This restructuring would streamline the architecture, enhance maintainability, and increase scalability.

Proposed Solution

  • Merge the functionalities of zmsdb, zmsapi, zmsentities, zmsclient, and zmsslim into a single backend module.
  • Rebuild the unified module using Laravel or Spring Boot to leverage their strengths in modular design, scalability, and ease of development.

Key Benefits

  1. Improved Structure: Simplifies navigation and organization of the backend.
  2. Enhanced Maintainability: Reduces redundancy and simplifies future updates.
  3. Scalability: Enables the backend to handle increased loads and requirements more effectively.
  4. Broader Appeal: Improves adoption potential among other cities and communes by showcasing a modern, efficient, and cohesive architecture.

I believe this overhaul will not only address existing challenges but also make the system more attractive to a wider audience. Feedback and suggestions are welcome!

@ThomasAFink ThomasAFink added enhancement New feature or request php Pull requests that update Php code labels Dec 10, 2024
@it-at-m it-at-m deleted a comment from coderabbitai bot Dec 10, 2024
@ThomasAFink
Copy link
Member Author

@coderabbitai how would you do this with Laravel? Give a visual concept.

Copy link
Contributor

coderabbitai bot commented Dec 10, 2024

Tip

For best results, initiate chat on the files or code changes.

Let me help you visualize how we could restructure the backend using Laravel. I'll create a diagram showing the proposed architecture and explain the key components.

@ThomasAFink Here's a conceptual overview of how we could reorganize the current components into a Laravel-based architecture:

graph TB
    subgraph "Laravel Backend"
        RT[Routes] --> MC[Controllers]
        MC --> MS[Services]
        MS --> MR[Repositories]
        MR --> MM[Models]
        MM --> DB[(Database)]
        
        subgraph "Current Components Mapping"
            MC -.- |zmsapi| C1[Controllers Layer]
            MS -.- |zmsclient| C2[Service Layer]
            MM -.- |zmsentities| C3[Models Layer]
            MR -.- |zmsdb| C4[Database Layer]
        end
        
        subgraph "API Middleware"
            AUTH[Authentication]
            THROT[Rate Limiting]
            LOG[Logging]
            AUTH & THROT & LOG --> RT
        end
    end

    API[API Consumers] --> RT
Loading

Here's how we would map the current components to Laravel's architecture:

  1. Models Layer (replaces zmsentities)
// app/Models/Appointment.php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Appointment extends Model
{
    protected $fillable = [
        'client_id',
        'service_id',
        'appointment_date',
        'status'
    ];
    
    // Relationships
    public function client()
    {
        return $this->belongsTo(Client::class);
    }
    
    public function service()
    {
        return $this->belongsTo(Service::class);
    }
}
  1. Repository Layer (replaces zmsdb)
// app/Repositories/AppointmentRepository.php
namespace App\Repositories;

use App\Models\Appointment;

class AppointmentRepository
{
    public function findAvailableSlots($serviceId, $date)
    {
        return Appointment::where('service_id', $serviceId)
            ->whereDate('appointment_date', $date)
            ->where('status', 'available')
            ->get();
    }
}
  1. Service Layer (replaces zmsclient)
// app/Services/AppointmentService.php
namespace App\Services;

use App\Repositories\AppointmentRepository;

class AppointmentService
{
    private $repository;
    
    public function __construct(AppointmentRepository $repository)
    {
        $this->repository = $repository;
    }
    
    public function bookAppointment($clientId, $serviceId, $date)
    {
        // Business logic here
        $availableSlots = $this->repository->findAvailableSlots($serviceId, $date);
        // Validation and booking logic
    }
}
  1. Controllers Layer (replaces zmsapi)
// app/Http/Controllers/Api/AppointmentController.php
namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Services\AppointmentService;
use App\Http\Requests\BookAppointmentRequest;

class AppointmentController extends Controller
{
    private $appointmentService;
    
    public function __construct(AppointmentService $appointmentService)
    {
        $this->appointmentService = $appointmentService;
    }
    
    public function book(BookAppointmentRequest $request)
    {
        $appointment = $this->appointmentService->bookAppointment(
            $request->client_id,
            $request->service_id,
            $request->date
        );
        
        return response()->json($appointment);
    }
}

Key Benefits of this Laravel Structure:

  1. Dependency Injection: Laravel's service container handles dependencies automatically
  2. Eloquent ORM: Powerful database interactions with relationships
  3. Middleware: Built-in authentication, rate limiting, and logging
  4. API Resources: Easy transformation of models to JSON responses
  5. Database Migrations: Version control for database schema
  6. Testing: Built-in testing framework with database transactions
  7. Queue System: Handle long-running tasks asynchronously
  8. Cache: Built-in caching system for performance optimization

The zmsslim functionality would be replaced by Laravel's robust routing and middleware system, which provides more features out of the box.

Would you like me to elaborate on any specific part of this architecture or show more detailed code examples for specific components?

@ThomasAFink
Copy link
Member Author

Yes, @coderabbitai go into further depth about the Laravel file structure.

Copy link
Contributor

coderabbitai bot commented Dec 10, 2024

Tip

For best results, initiate chat on the files or code changes.

@ThomasAFink I'll provide a detailed overview of the Laravel file structure that we would use for the eAppointment system refactoring:

graph TD
    subgraph "Laravel Project Root"
        A[app/] --> M[Models/]
        A --> C[Http/Controllers/]
        A --> R[Repositories/]
        A --> S[Services/]
        A --> P[Providers/]
        A --> E[Events/]
        A --> L[Listeners/]
        
        C --> API[Api/]
        
        subgraph "Configuration"
            CF[config/]
        end
        
        subgraph "Database"
            D[database/] --> MI[migrations/]
            D --> SE[seeders/]
            D --> FA[factories/]
        end
        
        subgraph "Resources"
            RE[resources/] --> V[views/]
            RE --> LA[lang/]
        end
        
        subgraph "Routes"
            RO[routes/] --> WE[web.php]
            RO --> AP[api.php]
        end
        
        subgraph "Tests"
            T[tests/] --> U[Unit/]
            T --> FE[Feature/]
        end
    end
Loading

Here's a detailed breakdown of each directory and its purpose in our eAppointment system:

eappointment/
├── app/                                # Application core code
│   ├── Console/                        # Custom Artisan commands
│   │   └── Commands/
│   │       └── GenerateAvailabilityCommand.php
│   ├── Events/                         # Event classes
│   │   ├── AppointmentBooked.php
│   │   └── SlotAvailabilityChanged.php
│   ├── Exceptions/                     # Custom exception handlers
│   │   └── AppointmentNotFoundException.php
│   ├── Http/                           # HTTP layer
│   │   ├── Controllers/               
│   │   │   └── Api/
│   │   │       ├── AppointmentController.php
│   │   │       ├── ServiceController.php
│   │   │       └── ClientController.php
│   │   ├── Middleware/                 # Custom middleware
│   │   │   └── ValidateAppointmentSlot.php
│   │   └── Requests/                   # Form requests
│   │       └── BookAppointmentRequest.php
│   ├── Jobs/                           # Queue jobs
│   │   └── SendAppointmentReminder.php
│   ├── Listeners/                      # Event listeners
│   │   └── NotifyClientAboutBooking.php
│   ├── Models/                         # Eloquent models
│   │   ├── Appointment.php
│   │   ├── Client.php
│   │   └── Service.php
│   ├── Notifications/                  # Notification classes
│   │   └── AppointmentConfirmation.php
│   ├── Policies/                       # Authorization policies
│   │   └── AppointmentPolicy.php
│   ├── Providers/                      # Service providers
│   │   └── AppointmentServiceProvider.php
│   ├── Repositories/                   # Repository pattern
│   │   ├── Contracts/
│   │   │   └── AppointmentRepositoryInterface.php
│   │   └── AppointmentRepository.php
│   └── Services/                       # Business logic
│       └── AppointmentService.php
├── bootstrap/                          # Framework bootstrap files
├── config/                             # Configuration files
│   ├── app.php
│   ├── auth.php
│   └── services.php
├── database/                           # Database
│   ├── factories/                      # Model factories for testing
│   │   └── AppointmentFactory.php
│   ├── migrations/                     # Database migrations
│   │   ├── create_clients_table.php
│   │   ├── create_services_table.php
│   │   └── create_appointments_table.php
│   └── seeders/                        # Database seeders
│       └── ServiceSeeder.php
├── public/                             # Public assets
├── resources/                          # Views and raw assets
│   ├── js/
│   ├── css/
│   ├── lang/                          # Localization files
│   │   ├── en/
│   │   └── de/
│   └── views/                         # Blade templates (if needed)
├── routes/                            # Route definitions
│   ├── api.php                        # API routes
│   ├── channels.php                   # Broadcasting channels
│   └── web.php                        # Web routes
├── storage/                           # Application storage
├── tests/                             # Test files
│   ├── Feature/                       # Feature tests
│   │   └── AppointmentBookingTest.php
│   └── Unit/                          # Unit tests
│       └── AppointmentServiceTest.php
├── .env                               # Environment variables
├── .env.example                       # Environment example
├── composer.json                      # PHP dependencies
└── phpunit.xml                        # PHPUnit configuration

Key aspects of this structure for the eAppointment system:

  1. API Routes (routes/api.php):
Route::prefix('v1')->group(function () {
    Route::apiResource('appointments', AppointmentController::class);
    Route::apiResource('services', ServiceController::class);
    Route::apiResource('clients', ClientController::class);
    Route::get('slots/available', [AppointmentController::class, 'getAvailableSlots']);
});
  1. Service Provider (app/Providers/AppointmentServiceProvider.php):
namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Repositories\Contracts\AppointmentRepositoryInterface;
use App\Repositories\AppointmentRepository;

class AppointmentServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->bind(AppointmentRepositoryInterface::class, AppointmentRepository::class);
    }
}
  1. Event and Listener (Event-driven architecture):
// app/Events/AppointmentBooked.php
class AppointmentBooked
{
    public $appointment;
    
    public function __construct(Appointment $appointment)
    {
        $this->appointment = $appointment;
    }
}

// app/Listeners/NotifyClientAboutBooking.php
class NotifyClientAboutBooking
{
    public function handle(AppointmentBooked $event)
    {
        $event->appointment->client->notify(
            new AppointmentConfirmation($event->appointment)
        );
    }
}
  1. Database Migration (database/migrations/create_appointments_table.php):
public function up()
{
    Schema::create('appointments', function (Blueprint $table) {
        $table->id();
        $table->foreignId('client_id')->constrained();
        $table->foreignId('service_id')->constrained();
        $table->dateTime('start_time');
        $table->dateTime('end_time');
        $table->string('status');
        $table->text('notes')->nullable();
        $table->timestamps();
        $table->softDeletes();
    });
}
  1. Feature Test (tests/Feature/AppointmentBookingTest.php):
public function test_can_book_available_appointment()
{
    $client = Client::factory()->create();
    $service = Service::factory()->create();
    
    $response = $this->postJson('/api/v1/appointments', [
        'client_id' => $client->id,
        'service_id' => $service->id,
        'start_time' => now()->addDay(),
    ]);
    
    $response->assertStatus(201)
        ->assertJsonStructure(['id', 'start_time', 'status']);
}

This structure provides several advantages:

  • Clear separation of concerns
  • Easy to maintain and scale
  • Built-in testing capabilities
  • Event-driven architecture for decoupled components
  • Robust dependency injection system
  • Database migration and seeding for version control
  • Multi-language support
  • Comprehensive API documentation generation

Would you like me to elaborate on any specific component or show more implementation details for any particular feature?

@ThomasAFink
Copy link
Member Author

ThomasAFink commented Dec 10, 2024

@coderabbitai there wouldn't be any css or js in the backend. It would have to remain compatible to the frontend PHP/Twig modules zmsadmin, zmsstatistic, zmscalldisplay and zmsticketprinter which all currently build upon the zmsapi. The frontend modules would later be refactored as seperate VueJs modules like the new zmscitizenview in the feat-zms-2936-initial-setup-webcomponent branch.

Copy link
Contributor

coderabbitai bot commented Dec 10, 2024

Tip

For best results, initiate chat on the files or code changes.

@ThomasAFink Ah, you're absolutely right! Let me revise the Laravel structure to focus purely on the backend API that needs to remain compatible with the existing frontend modules. Here's the updated architecture:

graph TB
    subgraph "Laravel Backend API"
        RT[API Routes] --> MC[Controllers]
        MC --> MS[Services]
        MS --> MR[Repositories]
        MR --> MM[Models]
        MM --> DB[(Database)]
        
        subgraph "API Compatibility Layer"
            CL[Legacy API Endpoints]
            TR[Response Transformers]
            CL --> MC
            MC --> TR
        end
    end
    
    subgraph "Current Frontend Modules"
        ZA[zmsadmin<br/>PHP/Twig] --> CL
        ZS[zmsstatistic<br/>PHP/Twig] --> CL
        ZC[zmscalldisplay<br/>PHP/Twig] --> CL
        ZT[zmsticketprinter<br/>PHP/Twig] --> CL
    end
    
    subgraph "Future Frontend"
        VF[Vue.js Modules<br/>like zmscitizenview] --> RT
    end
Loading

Here's the simplified Laravel structure focusing only on the API components:

eappointment-backend/
├── app/
│   ├── Http/
│   │   ├── Controllers/
│   │   │   └── Api/
│   │   │       ├── V1/                     # Legacy API endpoints
│   │   │       │   ├── AppointmentController.php
│   │   │       │   └── ServiceController.php
│   │   │       └── V2/                     # New API for Vue.js frontends
│   │   ├── Middleware/
│   │   └── Resources/                      # API response transformers
│   │       └── V1/
│   │           └── AppointmentResource.php
│   ├── Models/
│   ├── Repositories/
│   ├── Services/
│   └── Providers/
├── config/
├── database/
│   ├── migrations/
│   └── seeders/
├── routes/
│   └── api.php                             # API routes only
└── tests/

Example of maintaining API compatibility:

  1. Legacy API Route Structure (routes/api.php):
Route::prefix('v1')->group(function () {
    // Maintain existing zmsapi endpoints structure
    Route::get('scope/{scope}/appointments', [AppointmentController::class, 'index']);
    Route::get('scope/{scope}/services', [ServiceController::class, 'index']);
});

Route::prefix('v2')->group(function () {
    // New RESTful API for Vue.js frontends
    Route::apiResource('appointments', AppointmentControllerV2::class);
});
  1. Response Transformer (app/Http/Resources/V1/AppointmentResource.php):
class AppointmentResource extends JsonResource
{
    public function toArray($request)
    {
        // Transform to match existing zmsapi response format
        return [
            'id' => $this->id,
            'scope' => $this->scope,
            'status' => $this->status,
            // ... maintain the exact same response structure
        ];
    }
}
  1. Legacy API Controller (app/Http/Controllers/Api/V1/AppointmentController.php):
class AppointmentController extends Controller
{
    public function index($scope)
    {
        // Maintain compatibility with existing frontend modules
        $appointments = $this->appointmentService
            ->getByScopeWithLegacyFormat($scope);
            
        return AppointmentResource::collection($appointments);
    }
}

This approach allows us to:

  1. Maintain backward compatibility for existing PHP/Twig frontends
  2. Provide a clean API for future Vue.js modules
  3. Gradually migrate frontend modules without breaking changes
  4. Keep the backend focused purely on API functionality

Would you like me to elaborate on any specific aspect of the API compatibility layer or show how we would handle specific endpoints that the current frontend modules depend on?


✏️ Learnings added
Learnt from: ThomasAFink
URL: https://github.com/it-at-m/eappointment/issues/730

Timestamp: 2024-12-10T17:43:35.930Z
Learning: The backend refactoring to Laravel must maintain API compatibility with existing PHP/Twig frontend modules (zmsadmin, zmsstatistic, zmscalldisplay, zmsticketprinter) while supporting future Vue.js modules like zmscitizenview.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@it-at-m it-at-m deleted a comment from coderabbitai bot Dec 10, 2024
@ThomasAFink
Copy link
Member Author

@coderabbitai give a full example using scope. From the model to the queries, to the controllers and to the routes.

Copy link
Contributor

coderabbitai bot commented Dec 10, 2024

Tip

For best results, initiate chat on the files or code changes.

@ThomasAFink Here's a complete example of implementing the scope functionality in Laravel, from the model layer all the way to the routes:

  1. Model (app/Models/Scope.php):
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;

class Scope extends Model
{
    protected $fillable = [
        'name',
        'department',
        'status',
        'contact_email',
        'settings'
    ];

    protected $casts = [
        'settings' => 'array',
        'status' => 'boolean'
    ];

    // Relationships
    public function appointments(): HasMany
    {
        return $this->hasMany(Appointment::class);
    }

    public function services(): HasMany
    {
        return $this->hasMany(Service::class);
    }
}
  1. Repository (app/Repositories/ScopeRepository.php):
namespace App\Repositories;

use App\Models\Scope;
use Illuminate\Database\Eloquent\Collection;

class ScopeRepository
{
    public function findByName(string $scopeName): ?Scope
    {
        return Scope::where('name', $scopeName)
            ->where('status', true)
            ->first();
    }

    public function getActiveScopes(): Collection
    {
        return Scope::where('status', true)->get();
    }

    public function getScopeWithRelations(string $scopeName): ?Scope
    {
        return Scope::where('name', $scopeName)
            ->with(['appointments', 'services'])
            ->first();
    }
}
  1. Service (app/Services/ScopeService.php):
namespace App\Services;

use App\Repositories\ScopeRepository;
use App\Exceptions\ScopeNotFoundException;

class ScopeService
{
    private $scopeRepository;

    public function __construct(ScopeRepository $scopeRepository)
    {
        $this->scopeRepository = $scopeRepository;
    }

    public function getScope(string $scopeName)
    {
        $scope = $this->scopeRepository->findByName($scopeName);

        if (!$scope) {
            throw new ScopeNotFoundException("Scope '{$scopeName}' not found");
        }

        return $scope;
    }

    public function getScopeAppointments(string $scopeName)
    {
        $scope = $this->scopeRepository->getScopeWithRelations($scopeName);

        if (!$scope) {
            throw new ScopeNotFoundException("Scope '{$scopeName}' not found");
        }

        return $scope->appointments;
    }

    public function getScopeServices(string $scopeName)
    {
        $scope = $this->scopeRepository->getScopeWithRelations($scopeName);

        if (!$scope) {
            throw new ScopeNotFoundException("Scope '{$scopeName}' not found");
        }

        return $scope->services;
    }
}
  1. Controllers (app/Http/Controllers/Api/V1/):
namespace App\Http\Controllers\Api\V1;

use App\Http\Controllers\Controller;
use App\Services\ScopeService;
use App\Http\Resources\V1\AppointmentResource;
use App\Http\Resources\V1\ServiceResource;

class ScopeController extends Controller
{
    private $scopeService;

    public function __construct(ScopeService $scopeService)
    {
        $this->scopeService = $scopeService;
    }

    public function appointments(string $scope)
    {
        $appointments = $this->scopeService->getScopeAppointments($scope);
        
        return AppointmentResource::collection($appointments);
    }

    public function services(string $scope)
    {
        $services = $this->scopeService->getScopeServices($scope);
        
        return ServiceResource::collection($services);
    }
}
  1. Resources (app/Http/Resources/V1/):
namespace App\Http\Resources\V1;

use Illuminate\Http\Resources\Json\JsonResource;

class AppointmentResource extends JsonResource
{
    public function toArray($request)
    {
        // Match the existing zmsapi response format
        return [
            'id' => $this->id,
            'scope' => $this->scope->name,
            'client' => [
                'email' => $this->client_email,
                'telephone' => $this->client_telephone
            ],
            'status' => $this->status,
            'date' => $this->appointment_date->format('Y-m-d'),
            'time' => $this->appointment_date->format('H:i:s'),
            'service' => [
                'id' => $this->service->id,
                'name' => $this->service->name
            ]
        ];
    }
}
  1. Routes (routes/api.php):
use App\Http\Controllers\Api\V1\ScopeController;

Route::prefix('v1')->group(function () {
    // Legacy API endpoints for existing PHP/Twig frontends
    Route::get('scope/{scope}/appointments', [ScopeController::class, 'appointments']);
    Route::get('scope/{scope}/services', [ScopeController::class, 'services']);
});
  1. Migration (database/migrations/create_scopes_table.php):
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateScopesTable extends Migration
{
    public function up()
    {
        Schema::create('scopes', function (Blueprint $table) {
            $table->id();
            $table->string('name')->unique();
            $table->string('department');
            $table->boolean('status')->default(true);
            $table->string('contact_email');
            $table->json('settings')->nullable();
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('scopes');
    }
}
  1. Exception (app/Exceptions/ScopeNotFoundException.php):
namespace App\Exceptions;

use Exception;

class ScopeNotFoundException extends Exception
{
    protected $message = 'The requested scope was not found.';
    protected $code = 404;
}

This implementation:

  1. Maintains compatibility with existing frontend modules
  2. Uses Laravel's best practices for database interactions
  3. Implements proper separation of concerns
  4. Provides clear error handling
  5. Uses resource classes to transform data to match the existing API format

Would you like me to elaborate on any specific part or show how to implement additional scope-related functionality?


✏️ Learnings added
Learnt from: ThomasAFink
URL: https://github.com/it-at-m/eappointment/issues/730

Timestamp: 2024-12-10T18:33:24.968Z
Learning: In the Laravel refactoring, the scope functionality is a core concept that requires maintaining compatibility with existing frontend modules through proper model relationships, repositories, and API endpoints that match the current zmsapi format.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request php Pull requests that update Php code
Projects
None yet
Development

No branches or pull requests

1 participant