-
Notifications
You must be signed in to change notification settings - Fork 1
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
Comments
@coderabbitai how would you do this with Laravel? Give a visual concept. |
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
Here's how we would map the current components to Laravel's architecture:
// 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);
}
}
// 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();
}
}
// 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
}
}
// 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:
The Would you like me to elaborate on any specific part of this architecture or show more detailed code examples for specific components? |
Yes, @coderabbitai go into further depth about the Laravel file structure. |
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
Here's a detailed breakdown of each directory and its purpose in our eAppointment system:
Key aspects of this structure for the eAppointment system:
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']);
});
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);
}
}
// 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)
);
}
}
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();
});
}
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:
Would you like me to elaborate on any specific component or show more implementation details for any particular feature? |
@coderabbitai there wouldn't be any css or js in the backend. It would have to remain compatible to the frontend PHP/Twig modules |
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
Here's the simplified Laravel structure focusing only on the API components:
Example of maintaining API compatibility:
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);
});
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
];
}
}
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:
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
|
@coderabbitai give a full example using |
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:
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);
}
}
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();
}
}
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;
}
}
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);
}
}
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
]
];
}
}
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']);
});
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');
}
}
namespace App\Exceptions;
use Exception;
class ScopeNotFoundException extends Exception
{
protected $message = 'The requested scope was not found.';
protected $code = 404;
} This implementation:
Would you like me to elaborate on any specific part or show how to implement additional scope-related functionality? ✏️ Learnings added
|
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:
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
zmsdb
,zmsapi
,zmsentities
,zmsclient
, andzmsslim
into a single backend module.Key Benefits
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!
The text was updated successfully, but these errors were encountered: