UML Mini Series Part 5: Class Diagram & Laravel Realization
June 30, 2026 · tutorial · 39 min read
BySESE Lab
Last updated June 30, 2026
1. What is a Class Diagram?
A Class Diagram is a structure diagram that shows the static architecture of a system. It describes the classes (or entities) in the system, their attributes (properties), operations (methods), and the relationships between them: inheritance, composition, aggregation, and associations with multiplicity constraints.
While the previous four parts focused on behaviour (what the system does), the class diagram focuses on structure (what the system is made of). It is the diagram that developers refer to most during implementation, and it maps directly to code.
Key Elements of a Class Diagram
| Element | Notation | Meaning |
|---|---|---|
| Class | Rectangle with three compartments (name, attributes, operations) | A blueprint for objects. Top: class name. Middle: attributes with types. Bottom: methods with parameter and return types. |
| Association | Solid line between classes | A semantic relationship. “A Student enrols in a Course.” |
| Multiplicity | Numbers at association ends (1, 0.., 1..) | How many instances participate. “One Student can enrol in 0 or more Courses. One Course can have 0 or more Enrolments.” |
| Aggregation | Hollow diamond at the whole end | ”Has-a” relationship where the part can exist independently. “A Course has a Schedule, but a Schedule can exist without a Course.” |
| Composition | Filled diamond at the whole end | ”Has-a” relationship where the part cannot exist without the whole. “An Enrolment contains a Payment; if the Enrolment is deleted, the Payment is deleted too.” |
| Generalisation | Hollow triangle arrowhead pointing to the parent | Inheritance. “Student is a type of User.” “Lecturer is a type of User.” |
1. Apa Itu Class Diagram?
Class Diagram adalah diagram struktur yang menunjukkan arsitektur statis dari sebuah sistem. Diagram ini mendeskripsikan kelas (atau entitas) dalam sistem, atribut (properti), operasi (method), dan relasi di antaranya: inheritance, composition, aggregation, dan association dengan batasan multiplisitas.
Sementara empat bagian sebelumnya berfokus pada perilaku (apa yang dilakukan sistem), class diagram berfokus pada struktur (dari apa sistem dibuat). Ini adalah diagram yang paling sering dirujuk oleh developer selama implementasi, dan langsung dipetakan ke kode.
Elemen Kunci Class Diagram
| Elemen | Notasi | Makna |
|---|---|---|
| Class | Persegi panjang dengan tiga kompartemen (nama, atribut, operasi) | Blueprint untuk objek. Atas: nama kelas. Tengah: atribut dengan tipe. Bawah: method dengan tipe parameter dan return. |
| Association | Garis solid antar kelas | Hubungan semantik. “Seorang Student mendaftar di Course.” |
| Multiplicity | Angka di ujung association (1, 0.., 1..) | Berapa banyak instance yang berpartisipasi. “Satu Student dapat mendaftar di 0 atau lebih Course. Satu Course dapat memiliki 0 atau lebih Enrolment.” |
| Aggregation | Diamond kosong di ujung whole | Hubungan “has-a” yang memungkinkan bagian berdiri sendiri secara independen. “Course memiliki Schedule, tetapi Schedule dapat tetap ada tanpa Course.” |
| Composition | Diamond terisi di ujung whole | Hubungan “has-a” yang tidak memungkinkan bagian berdiri sendiri tanpa whole. “Enrolment berisi Payment; jika Enrolment dihapus, Payment juga dihapus.” |
| Generalisation | Kepala panah segitiga kosong menunjuk ke parent | Inheritance. “Student adalah tipe dari User.” “Lecturer adalah tipe dari User.” |
2. Why Use Class Diagrams?
What is it?
A class diagram is the structural blueprint of the system. It defines what classes exist, what data they hold, how they relate, and what operations they expose. It answers: “What are the building blocks, and how do they fit together?”
Why does it matter?
- Code generation. Class diagrams map nearly 1:1 to object-oriented code. Each class becomes a file; each attribute becomes a property; each association becomes a relationship (foreign key, join table, or reference).
- Database schema design. The class diagram’s associations with multiplicities directly inform the database schema: one-to-many becomes a foreign key, many-to-many becomes a pivot table.
- Communication. A class diagram is the quickest way for a new developer to understand the domain model. In 30 seconds, they can see that
Userhas subclassesStudentandLecturer, thatEnrolmentlinksStudenttoCourse, and thatPaymentis part ofEnrolment. - Refactoring safety. When you know the intended structure, you can spot violations: a direct dependency between two classes that should not know about each other, or a missing class that should exist based on the domain.
When do you use it?
Create a class diagram during the design phase, after the behavioural diagrams (use case, activity, sequence) are complete. It is the last UML diagram before coding begins, and the one that developers reference throughout implementation.
Where does it fit?
Class diagrams appear in architecture documentation, API documentation, and database design documents. In many teams, they are also embedded in the project README.
How do you create one?
- Collect all nouns from the use case scenario and sequence diagram: these are candidate classes.
- Identify attributes for each class based on the data flowing through messages in the sequence diagram.
- Identify operations (methods) based on the messages arriving at each lifeline.
- Define relationships and multiplicities between classes.
- Apply inheritance where classes share common structure and behaviour.
- Refine the diagram iteratively: the first draft is never the final model.
2. Mengapa Menggunakan Class Diagram?
Apa itu?
Class diagram adalah blueprint struktural sistem. Diagram ini mendefinisikan kelas apa yang ada, data apa yang mereka simpan, bagaimana mereka berelasi, dan operasi apa yang mereka ekspos. Diagram ini menjawab: “Apa saja blok bangunannya, dan bagaimana mereka saling cocok?”
Mengapa penting?
- Code generation. Class diagram dipetakan hampir 1:1 ke kode berorientasi objek. Setiap kelas menjadi file; setiap atribut menjadi properti; setiap association menjadi relasi (foreign key, join table, atau referensi).
- Desain skema database. Asosiasi pada class diagram beserta multiplisitasnya secara langsung menentukan skema database: one-to-many menjadi foreign key, many-to-many menjadi pivot table.
- Komunikasi. Class diagram adalah cara tercepat bagi developer baru untuk memahami domain model. Dalam 30 detik, mereka dapat melihat bahwa
Usermemiliki subclassStudentdanLecturer, bahwaEnrolmentmenghubungkanStudentkeCourse, dan bahwaPaymentadalah bagian dariEnrolment. - Keamanan refactoring. Ketika Anda mengetahui struktur yang dimaksud, Anda dapat menemukan pelanggaran: dependensi langsung antara dua kelas yang seharusnya tidak saling mengenal, atau kelas yang seharusnya ada berdasarkan domain namun belum dibuat.
Kapan digunakan?
Buat class diagram selama fase desain, setelah diagram perilaku (use case, activity, sequence) selesai. Ini adalah diagram UML terakhir sebelum coding dimulai, dan yang dirujuk developer sepanjang implementasi.
Di mana tempatnya?
Class diagram muncul di dokumentasi arsitektur, dokumentasi API, dan dokumen desain database. Di banyak tim, diagram ini juga disematkan di README proyek.
Bagaimana membuatnya?
- Kumpulkan semua kata benda dari use case scenario dan sequence diagram: ini adalah kandidat kelas.
- Identifikasi atribut untuk setiap kelas berdasarkan data yang mengalir melalui pesan di sequence diagram.
- Identifikasi operasi (method) berdasarkan pesan yang tiba di setiap lifeline.
- Definisikan relasi dan multiplisitas antar kelas.
- Terapkan inheritance ketika kelas berbagi struktur dan perilaku yang sama.
- Perbaiki diagram secara iteratif: draf pertama bukanlah model akhir.
3. Class Diagram: Campus Course Registration System
Below is the complete domain model. It captures every class discovered from the previous four parts and defines their attributes, methods, and relationships.
Relationship Summary
| Relationship | Type | Multiplicity | Meaning |
|---|---|---|---|
| User ← Student | Generalisation (inheritance) | N/A | Student inherits all User attributes and methods |
| User ← Lecturer | Generalisation | N/A | Lecturer inherits from User |
| User ← Admin | Generalisation | N/A | Admin inherits from User |
| Course → Lecturer | Association | 0..* to 1 | Each course is taught by exactly one lecturer; a lecturer can teach many courses |
| Student → Enrolment | Association | 1 to 0..* | A student can have zero or more enrolments |
| Course → Enrolment | Association | 1 to 0..* | A course can have zero or more enrolments (one per student) |
| Enrolment → Payment | Composition | 1 to 1 | Every enrolment has exactly one payment; payment cannot exist without enrolment |
| Student → Schedule | Association | 1 to 1 | Each student has one schedule |
| EnrolmentController → Services | Dependency | N/A | The controller orchestrates Laravel services instead of querying models directly |
| Form Requests → Controller | Dependency | N/A | Request classes validate the two HTTP messages from the sequence diagram |
| Resources → Models | Dependency | N/A | API resources transform models into familiar Laravel response payloads |
In the compact Laravel implementation below, Schedule is treated as a read model derived from a student’s enrolments rather than a separate persisted table. This keeps the sample focused on the enrolment transaction while preserving the conceptual domain view from the class diagram.
3. Class Diagram: Sistem Pendaftaran Mata Kuliah Kampus
Berikut adalah domain model lengkap. Diagram ini menangkap setiap kelas yang ditemukan dari empat bagian sebelumnya dan mendefinisikan atribut, method, dan relasinya.
Ringkasan Relasi
| Relasi | Tipe | Multiplisitas | Makna |
|---|---|---|---|
| User ← Student | Generalisation (inheritance) | N/A | Student mewarisi semua atribut dan method User |
| User ← Lecturer | Generalisation | N/A | Lecturer mewarisi dari User |
| User ← Admin | Generalisation | N/A | Admin mewarisi dari User |
| Course → Lecturer | Association | 0..* ke 1 | Setiap course diajar oleh tepat satu dosen; seorang dosen dapat mengajar banyak course |
| Student → Enrolment | Association | 1 ke 0..* | Seorang mahasiswa dapat memiliki nol atau lebih pendaftaran |
| Course → Enrolment | Association | 1 ke 0..* | Sebuah course dapat memiliki nol atau lebih pendaftaran (satu per mahasiswa) |
| Enrolment → Payment | Composition | 1 ke 1 | Setiap pendaftaran memiliki tepat satu pembayaran; pembayaran tidak dapat ada tanpa pendaftaran |
| Student → Schedule | Association | 1 ke 1 | Setiap mahasiswa memiliki satu jadwal |
| EnrolmentController → Services | Dependency | N/A | Controller mengorkestrasi service Laravel, bukan langsung melakukan query ke model |
| Form Requests → Controller | Dependency | N/A | Request class memvalidasi kedua pesan HTTP dari sequence diagram |
| Resources → Models | Dependency | N/A | API resource mentransformasi model menjadi payload respons Laravel yang familiar |
Dalam implementasi Laravel ringkas di bawah, Schedule diperlakukan sebagai read model yang diturunkan dari enrolment mahasiswa, bukan sebagai tabel terpisah yang disimpan permanen di database. Ini menjaga contoh tetap fokus pada transaksi pendaftaran sambil mempertahankan tampilan domain konseptual dari class diagram.
4. Laravel Implementation
Now we implement the class diagram as a Laravel application. We will create:
- Database Migrations: the physical schema that realises the class attributes and relationships.
- Eloquent Models: the ORM layer that maps classes to database tables.
- Services: the business logic layer called by the controller in the sequence diagram.
- Controller: the HTTP layer that orchestrates the enrolment workflow.
4.1 Laravel Project Setup
composer create-project laravel/laravel campus-registration
cd campus-registration
php artisan make:model Student -m
php artisan make:model Lecturer -m
php artisan make:model Admin -m
php artisan make:model Course -m
php artisan make:model Enrolment -m
php artisan make:model Payment -m
php artisan make:request ShowEnrolmentSummaryRequest
php artisan make:request ConfirmEnrolmentRequest
php artisan make:resource CourseResource
php artisan make:resource EnrolmentResource
4.2 Database Migrations
Users table (built-in Laravel migration, extended):
<?php
// database/migrations/xxxx_create_users_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->string('password');
$table->enum('role', ['student', 'lecturer', 'admin'])->default('student');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('users');
}
};
Students table:
<?php
// database/migrations/xxxx_create_students_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('students', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->string('nim')->unique();
$table->string('major');
$table->integer('semester')->default(1);
$table->string('phone')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('students');
}
};
Courses table:
<?php
// database/migrations/xxxx_create_courses_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('courses', function (Blueprint $table) {
$table->id();
$table->string('code')->unique();
$table->string('name');
$table->integer('credits');
$table->string('day');
$table->string('time_slot');
$table->string('room');
$table->integer('quota');
$table->integer('enrolled_count')->default(0);
$table->decimal('fee', 12, 2);
$table->boolean('registration_open')->default(true);
$table->foreignId('lecturer_id')->constrained('lecturers')->cascadeOnDelete();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('courses');
}
};
Enrolments table:
<?php
// database/migrations/xxxx_create_enrolments_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('enrolments', function (Blueprint $table) {
$table->id();
$table->foreignId('student_id')->constrained('students')->cascadeOnDelete();
$table->foreignId('course_id')->constrained('courses')->cascadeOnDelete();
$table->string('status')->default('enrolled');
$table->string('transaction_id');
$table->timestamp('enrolled_at');
$table->timestamps();
$table->unique(['student_id', 'course_id']);
});
}
public function down(): void
{
Schema::dropIfExists('enrolments');
}
};
Payments table:
<?php
// database/migrations/xxxx_create_payments_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('payments', function (Blueprint $table) {
$table->id();
$table->foreignId('enrolment_id')->constrained('enrolments')->cascadeOnDelete();
$table->string('transaction_id')->unique();
$table->decimal('amount', 12, 2);
$table->string('status')->default('pending');
$table->timestamp('paid_at')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('payments');
}
};
4. Implementasi Laravel
Sekarang kita mengimplementasikan class diagram sebagai aplikasi Laravel. Kita akan membuat:
- Database Migrations: skema fisik yang merealisasikan atribut dan relasi kelas.
- Eloquent Models: lapisan ORM yang memetakan kelas ke tabel database.
- Services: lapisan logika bisnis yang dipanggil oleh controller di sequence diagram.
- Controller: lapisan HTTP yang mengorkestrasi alur kerja pendaftaran.
4.1 Setup Proyek Laravel
composer create-project laravel/laravel campus-registration
cd campus-registration
php artisan make:model Student -m
php artisan make:model Lecturer -m
php artisan make:model Admin -m
php artisan make:model Course -m
php artisan make:model Enrolment -m
php artisan make:model Payment -m
php artisan make:request ShowEnrolmentSummaryRequest
php artisan make:request ConfirmEnrolmentRequest
php artisan make:resource CourseResource
php artisan make:resource EnrolmentResource
4.2 Database Migrations
Tabel users (migration bawaan Laravel, diperluas):
<?php
// database/migrations/xxxx_create_users_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->string('password');
$table->enum('role', ['student', 'lecturer', 'admin'])->default('student');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('users');
}
};
Tabel students:
<?php
// database/migrations/xxxx_create_students_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('students', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->string('nim')->unique();
$table->string('major');
$table->integer('semester')->default(1);
$table->string('phone')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('students');
}
};
Tabel courses:
<?php
// database/migrations/xxxx_create_courses_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('courses', function (Blueprint $table) {
$table->id();
$table->string('code')->unique();
$table->string('name');
$table->integer('credits');
$table->string('day');
$table->string('time_slot');
$table->string('room');
$table->integer('quota');
$table->integer('enrolled_count')->default(0);
$table->decimal('fee', 12, 2);
$table->boolean('registration_open')->default(true);
$table->foreignId('lecturer_id')->constrained('lecturers')->cascadeOnDelete();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('courses');
}
};
Tabel enrolments:
<?php
// database/migrations/xxxx_create_enrolments_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('enrolments', function (Blueprint $table) {
$table->id();
$table->foreignId('student_id')->constrained('students')->cascadeOnDelete();
$table->foreignId('course_id')->constrained('courses')->cascadeOnDelete();
$table->string('status')->default('enrolled');
$table->string('transaction_id');
$table->timestamp('enrolled_at');
$table->timestamps();
$table->unique(['student_id', 'course_id']);
});
}
public function down(): void
{
Schema::dropIfExists('enrolments');
}
};
Tabel payments:
<?php
// database/migrations/xxxx_create_payments_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('payments', function (Blueprint $table) {
$table->id();
$table->foreignId('enrolment_id')->constrained('enrolments')->cascadeOnDelete();
$table->string('transaction_id')->unique();
$table->decimal('amount', 12, 2);
$table->string('status')->default('pending');
$table->timestamp('paid_at')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('payments');
}
};
4.3 Eloquent Models
User model: already provided by Laravel, add role handling and relationships:
<?php
// app/Models/User.php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Database\Eloquent\Relations\HasOne;
class User extends Authenticatable
{
protected $fillable = ['name', 'email', 'password', 'role'];
protected $hidden = ['password', 'remember_token'];
protected function casts(): array
{
return [
'password' => 'hashed',
];
}
public function student(): HasOne
{
return $this->hasOne(Student::class);
}
public function lecturer(): HasOne
{
return $this->hasOne(Lecturer::class);
}
public function admin(): HasOne
{
return $this->hasOne(Admin::class);
}
}
Student model:
<?php
// app/Models/Student.php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Student extends Model
{
protected $fillable = ['user_id', 'nim', 'major', 'semester', 'phone'];
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function enrolments(): HasMany
{
return $this->hasMany(Enrolment::class);
}
public function enrolledCourses()
{
return $this->belongsToMany(Course::class, 'enrolments', 'student_id', 'course_id')
->withTimestamps();
}
}
Course model:
<?php
// app/Models/Course.php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Course extends Model
{
protected $fillable = [
'code', 'name', 'credits', 'day', 'time_slot',
'room', 'quota', 'enrolled_count', 'fee',
'registration_open', 'lecturer_id',
];
protected function casts(): array
{
return [
'registration_open' => 'boolean',
'fee' => 'decimal:2',
'quota' => 'integer',
'enrolled_count' => 'integer',
];
}
public function lecturer(): BelongsTo
{
return $this->belongsTo(Lecturer::class);
}
public function enrolments(): HasMany
{
return $this->hasMany(Enrolment::class);
}
public function checkQuota(): bool
{
return $this->enrolled_count < $this->quota;
}
public function calculateFee(): float
{
return $this->fee;
}
/** Decrement available quota by recording one more enrolled student. */
public function decrementQuota(): void
{
$this->increment('enrolled_count');
}
}
Enrolment model:
<?php
// app/Models/Enrolment.php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasOne;
class Enrolment extends Model
{
protected $fillable = [
'student_id', 'course_id', 'status', 'transaction_id', 'enrolled_at',
];
protected function casts(): array
{
return [
'enrolled_at' => 'datetime',
];
}
public function student(): BelongsTo
{
return $this->belongsTo(Student::class);
}
public function course(): BelongsTo
{
return $this->belongsTo(Course::class);
}
public function payment(): HasOne
{
return $this->hasOne(Payment::class);
}
public static function checkConflict(int $studentId, int $courseId): ?Enrolment
{
$newCourse = Course::findOrFail($courseId);
return self::where('student_id', $studentId)
->whereHas('course', function ($query) use ($newCourse) {
$query->where('day', $newCourse->day)
->where('time_slot', $newCourse->time_slot);
})
->first();
}
}
Payment model:
<?php
// app/Models/Payment.php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Payment extends Model
{
protected $fillable = [
'enrolment_id', 'transaction_id', 'amount', 'status', 'paid_at',
];
protected function casts(): array
{
return [
'amount' => 'decimal:2',
'paid_at' => 'datetime',
];
}
public function enrolment(): BelongsTo
{
return $this->belongsTo(Enrolment::class);
}
}
4.3 Eloquent Models
Model User: sudah disediakan oleh Laravel, tambahkan penanganan role dan relasi:
<?php
// app/Models/User.php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Database\Eloquent\Relations\HasOne;
class User extends Authenticatable
{
protected $fillable = ['name', 'email', 'password', 'role'];
protected $hidden = ['password', 'remember_token'];
protected function casts(): array
{
return [
'password' => 'hashed',
];
}
public function student(): HasOne
{
return $this->hasOne(Student::class);
}
public function lecturer(): HasOne
{
return $this->hasOne(Lecturer::class);
}
public function admin(): HasOne
{
return $this->hasOne(Admin::class);
}
}
Model Student:
<?php
// app/Models/Student.php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Student extends Model
{
protected $fillable = ['user_id', 'nim', 'major', 'semester', 'phone'];
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function enrolments(): HasMany
{
return $this->hasMany(Enrolment::class);
}
public function enrolledCourses()
{
return $this->belongsToMany(Course::class, 'enrolments', 'student_id', 'course_id')
->withTimestamps();
}
}
Model Course:
<?php
// app/Models/Course.php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Course extends Model
{
protected $fillable = [
'code', 'name', 'credits', 'day', 'time_slot',
'room', 'quota', 'enrolled_count', 'fee',
'registration_open', 'lecturer_id',
];
protected function casts(): array
{
return [
'registration_open' => 'boolean',
'fee' => 'decimal:2',
'quota' => 'integer',
'enrolled_count' => 'integer',
];
}
public function lecturer(): BelongsTo
{
return $this->belongsTo(Lecturer::class);
}
public function enrolments(): HasMany
{
return $this->hasMany(Enrolment::class);
}
public function checkQuota(): bool
{
return $this->enrolled_count < $this->quota;
}
public function calculateFee(): float
{
return $this->fee;
}
/** Decrement available quota by recording one more enrolled student. */
public function decrementQuota(): void
{
$this->increment('enrolled_count');
}
}
Model Enrolment:
<?php
// app/Models/Enrolment.php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasOne;
class Enrolment extends Model
{
protected $fillable = [
'student_id', 'course_id', 'status', 'transaction_id', 'enrolled_at',
];
protected function casts(): array
{
return [
'enrolled_at' => 'datetime',
];
}
public function student(): BelongsTo
{
return $this->belongsTo(Student::class);
}
public function course(): BelongsTo
{
return $this->belongsTo(Course::class);
}
public function payment(): HasOne
{
return $this->hasOne(Payment::class);
}
public static function checkConflict(int $studentId, int $courseId): ?Enrolment
{
$newCourse = Course::findOrFail($courseId);
return self::where('student_id', $studentId)
->whereHas('course', function ($query) use ($newCourse) {
$query->where('day', $newCourse->day)
->where('time_slot', $newCourse->time_slot);
})
->first();
}
}
Model Payment:
<?php
// app/Models/Payment.php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Payment extends Model
{
protected $fillable = [
'enrolment_id', 'transaction_id', 'amount', 'status', 'paid_at',
];
protected function casts(): array
{
return [
'amount' => 'decimal:2',
'paid_at' => 'datetime',
];
}
public function enrolment(): BelongsTo
{
return $this->belongsTo(Enrolment::class);
}
}
4.4 Form Requests and API Resources
Laravel developers usually express request validation with Form Requests and response shaping with API Resources. These classes realise the request and response messages in the sequence diagram using familiar Laravel patterns.
ShowEnrolmentSummaryRequest:
<?php
// app/Http/Requests/ShowEnrolmentSummaryRequest.php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class ShowEnrolmentSummaryRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'course_id' => ['required', 'exists:courses,id'],
];
}
}
ConfirmEnrolmentRequest:
<?php
// app/Http/Requests/ConfirmEnrolmentRequest.php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class ConfirmEnrolmentRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'course_id' => ['required', 'exists:courses,id'],
];
}
}
CourseResource:
<?php
// app/Http/Resources/CourseResource.php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class CourseResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'code' => $this->code,
'name' => $this->name,
'credits' => $this->credits,
'schedule' => "{$this->day}, {$this->time_slot}",
'room' => $this->room,
'fee' => (float) $this->fee,
'available_seats' => $this->quota - $this->enrolled_count,
'lecturer' => $this->lecturer->user->name,
];
}
}
EnrolmentResource:
<?php
// app/Http/Resources/EnrolmentResource.php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class EnrolmentResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'course' => new CourseResource($this->whenLoaded('course')),
'status' => $this->status,
'transaction_id' => $this->transaction_id,
'enrolled_at' => $this->enrolled_at,
];
}
}
4.5 Service Layer
The CourseService and EnrolmentService encapsulate business logic. The controller delegates to them: it never queries the database directly.
CourseService:
<?php
// app/Services/CourseService.php
namespace App\Services;
use App\Models\Course;
class CourseService
{
public function getCourseDetails(int $courseId): Course
{
return Course::with('lecturer.user')->findOrFail($courseId);
}
public function checkQuota(int $courseId): bool
{
return Course::findOrFail($courseId)->checkQuota();
}
public function calculateFee(int $courseId, int $studentId): float
{
return Course::findOrFail($courseId)->calculateFee();
}
public function decrementQuota(int $courseId): void
{
$course = Course::findOrFail($courseId);
$course->decrementQuota();
}
}
EnrolmentService:
<?php
// app/Services/EnrolmentService.php
namespace App\Services;
use App\Models\Course;
use App\Models\Enrolment;
use App\Models\Payment;
use Illuminate\Support\Facades\DB;
class EnrolmentService
{
public function checkScheduleConflict(int $studentId, int $courseId): ?array
{
$conflict = Enrolment::checkConflict($studentId, $courseId);
if ($conflict) {
return [
'has_conflict' => true,
'conflicting_course_name' => $conflict->course->name,
];
}
return ['has_conflict' => false, 'conflicting_course_name' => null];
}
public function createPaidEnrolment(int $studentId, int $courseId, string $transactionId): Enrolment
{
return DB::transaction(function () use ($studentId, $courseId, $transactionId) {
$course = Course::findOrFail($courseId);
$enrolment = Enrolment::create([
'student_id' => $studentId,
'course_id' => $courseId,
'status' => 'enrolled',
'transaction_id' => $transactionId,
'enrolled_at' => now(),
]);
Payment::create([
'enrolment_id' => $enrolment->id,
'transaction_id' => $transactionId,
'amount' => $course->fee,
'status' => 'paid',
'paid_at' => now(),
]);
$course->decrementQuota();
return $enrolment->load('course');
});
}
}
4.6 Payment Gateway Integration
<?php
// app/Services/PaymentGateway.php
namespace App\Services;
use App\Exceptions\PaymentFailedException;
use Illuminate\Support\Str;
class PaymentGateway
{
/**
* Charge the student for course enrolment.
*
* In production, this would call Midtrans, Xendit, Stripe, or another
* payment provider's API. This implementation simulates the call.
*/
public function charge(float $amount, int $studentId, int $courseId): array
{
// Simulate external API call with a small chance of failure
if (random_int(0, 9) === 0) {
throw new PaymentFailedException('Insufficient funds');
}
return [
'success' => true,
'transaction_id' => 'TRX-' . Str::upper(Str::random(16)),
];
}
}
Custom exception:
<?php
// app/Exceptions/PaymentFailedException.php
namespace App\Exceptions;
use RuntimeException;
class PaymentFailedException extends RuntimeException {}
4.4 Form Request dan API Resource
Developer Laravel biasanya mengekspresikan validasi request dengan Form Request dan pembentukan respons dengan API Resource. Class ini merealisasikan pesan request dan response pada sequence diagram dengan pola Laravel yang familiar.
ShowEnrolmentSummaryRequest:
<?php
// app/Http/Requests/ShowEnrolmentSummaryRequest.php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class ShowEnrolmentSummaryRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'course_id' => ['required', 'exists:courses,id'],
];
}
}
ConfirmEnrolmentRequest:
<?php
// app/Http/Requests/ConfirmEnrolmentRequest.php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class ConfirmEnrolmentRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'course_id' => ['required', 'exists:courses,id'],
];
}
}
CourseResource:
<?php
// app/Http/Resources/CourseResource.php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class CourseResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'code' => $this->code,
'name' => $this->name,
'credits' => $this->credits,
'schedule' => "{$this->day}, {$this->time_slot}",
'room' => $this->room,
'fee' => (float) $this->fee,
'available_seats' => $this->quota - $this->enrolled_count,
'lecturer' => $this->lecturer->user->name,
];
}
}
EnrolmentResource:
<?php
// app/Http/Resources/EnrolmentResource.php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class EnrolmentResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'course' => new CourseResource($this->whenLoaded('course')),
'status' => $this->status,
'transaction_id' => $this->transaction_id,
'enrolled_at' => $this->enrolled_at,
];
}
}
4.5 Service Layer
CourseService dan EnrolmentService mengenkapsulasi logika bisnis. Controller mendelegasikan kepada mereka: controller tidak pernah melakukan query database secara langsung.
CourseService:
<?php
// app/Services/CourseService.php
namespace App\Services;
use App\Models\Course;
class CourseService
{
public function getCourseDetails(int $courseId): Course
{
return Course::with('lecturer.user')->findOrFail($courseId);
}
public function checkQuota(int $courseId): bool
{
return Course::findOrFail($courseId)->checkQuota();
}
public function calculateFee(int $courseId, int $studentId): float
{
return Course::findOrFail($courseId)->calculateFee();
}
public function decrementQuota(int $courseId): void
{
$course = Course::findOrFail($courseId);
$course->decrementQuota();
}
}
EnrolmentService:
<?php
// app/Services/EnrolmentService.php
namespace App\Services;
use App\Models\Course;
use App\Models\Enrolment;
use App\Models\Payment;
use Illuminate\Support\Facades\DB;
class EnrolmentService
{
public function checkScheduleConflict(int $studentId, int $courseId): ?array
{
$conflict = Enrolment::checkConflict($studentId, $courseId);
if ($conflict) {
return [
'has_conflict' => true,
'conflicting_course_name' => $conflict->course->name,
];
}
return ['has_conflict' => false, 'conflicting_course_name' => null];
}
public function createPaidEnrolment(int $studentId, int $courseId, string $transactionId): Enrolment
{
return DB::transaction(function () use ($studentId, $courseId, $transactionId) {
$course = Course::findOrFail($courseId);
$enrolment = Enrolment::create([
'student_id' => $studentId,
'course_id' => $courseId,
'status' => 'enrolled',
'transaction_id' => $transactionId,
'enrolled_at' => now(),
]);
Payment::create([
'enrolment_id' => $enrolment->id,
'transaction_id' => $transactionId,
'amount' => $course->fee,
'status' => 'paid',
'paid_at' => now(),
]);
$course->decrementQuota();
return $enrolment->load('course');
});
}
}
4.6 Integrasi Payment Gateway
<?php
// app/Services/PaymentGateway.php
namespace App\Services;
use App\Exceptions\PaymentFailedException;
use Illuminate\Support\Str;
class PaymentGateway
{
/**
* Menagih mahasiswa untuk pendaftaran mata kuliah.
*
* Di production, ini akan memanggil API Midtrans, Xendit, Stripe, atau
* penyedia pembayaran lainnya. Implementasi ini menyimulasikan panggilan.
*/
public function charge(float $amount, int $studentId, int $courseId): array
{
// Simulasi panggilan API eksternal dengan kemungkinan kecil gagal
if (random_int(0, 9) === 0) {
throw new PaymentFailedException('Dana tidak mencukupi');
}
return [
'success' => true,
'transaction_id' => 'TRX-' . Str::upper(Str::random(16)),
];
}
}
Exception kustom:
<?php
// app/Exceptions/PaymentFailedException.php
namespace App\Exceptions;
use RuntimeException;
class PaymentFailedException extends RuntimeException {}
4.7 EnrolmentController: The Complete Orchestrator
This controller realises the sequence diagram from Part 4. The controller messages correspond to method calls below, while persistence details are encapsulated by services.
<?php
// app/Http/Controllers/EnrolmentController.php
namespace App\Http\Controllers;
use App\Exceptions\PaymentFailedException;
use App\Http\Requests\ConfirmEnrolmentRequest;
use App\Http\Requests\ShowEnrolmentSummaryRequest;
use App\Http\Resources\CourseResource;
use App\Http\Resources\EnrolmentResource;
use App\Models\Student;
use App\Services\CourseService;
use App\Services\EnrolmentService;
use App\Services\PaymentGateway;
use Illuminate\Http\JsonResponse;
class EnrolmentController extends Controller
{
public function __construct(
private CourseService $courseService,
private EnrolmentService $enrolmentService,
private PaymentGateway $paymentGateway,
) {
$this->middleware('auth');
}
/**
* Step 6 of the sequence diagram: show enrolment summary.
* The student has selected a course and the system validates
* prerequisites before showing the confirmation screen.
*/
public function showSummary(ShowEnrolmentSummaryRequest $request): JsonResponse
{
$courseId = $request->integer('course_id');
$student = $this->getAuthenticatedStudent();
// Check quota (Activity Diagram decision node: D3)
if (!$this->courseService->checkQuota($courseId)) {
return response()->json([
'error' => 'Mata kuliah penuh. Silakan gabung daftar tunggu.',
], 400);
}
// Check schedule conflict (Activity Diagram decision node: D4)
$conflict = $this->enrolmentService->checkScheduleConflict(
$student->id, $courseId
);
if ($conflict['has_conflict']) {
return response()->json([
'error' => 'Konflik jadwal',
'conflict' => $conflict,
], 409);
}
// Calculate fee and build summary
$course = $this->courseService->getCourseDetails($courseId);
$fee = $this->courseService->calculateFee($courseId, $student->id);
return response()->json([
'course' => new CourseResource($course),
'fee' => $fee,
'message' => 'Silakan konfirmasi pendaftaran Anda.',
]);
}
/**
* Steps 8-13 of the sequence diagram: confirm enrolment,
* process payment, create records, and return success.
*/
public function confirm(ConfirmEnrolmentRequest $request): JsonResponse
{
$courseId = $request->integer('course_id');
$student = $this->getAuthenticatedStudent();
// Re-validate quota and conflict (race condition protection)
if (!$this->courseService->checkQuota($courseId)) {
return response()->json([
'error' => 'Mata kuliah sudah penuh.',
], 400);
}
$conflict = $this->enrolmentService->checkScheduleConflict(
$student->id, $courseId
);
if ($conflict['has_conflict']) {
return response()->json([
'error' => 'Konflik jadwal terdeteksi.',
'conflict' => $conflict,
], 409);
}
$fee = $this->courseService->calculateFee($courseId, $student->id);
// Process payment via external gateway
try {
$paymentResult = $this->paymentGateway->charge(
$fee, $student->id, $courseId
);
} catch (PaymentFailedException $e) {
return response()->json([
'error' => 'Pembayaran gagal: ' . $e->getMessage(),
], 402);
}
// Create enrolment (transactional: enrolment + payment + quota)
$enrolment = $this->enrolmentService->createPaidEnrolment(
$student->id,
$courseId,
$paymentResult['transaction_id']
);
// In a production application, dispatch a notification here.
// The response confirms the schedule can be refreshed from enrolments.
return response()->json([
'message' => 'Pendaftaran berhasil.',
'enrolment' => new EnrolmentResource($enrolment),
], 201);
}
private function getAuthenticatedStudent(): Student
{
/** @var \App\Models\User $user */
$user = auth()->user();
return $user->student ?? throw new \RuntimeException(
'Hanya mahasiswa yang dapat mendaftar mata kuliah.'
);
}
}
API Routes:
<?php
// routes/api.php
use App\Http\Controllers\EnrolmentController;
Route::middleware('auth:sanctum')->group(function () {
Route::post('/enrolments/summary', [EnrolmentController::class, 'showSummary']);
Route::post('/enrolments/confirm', [EnrolmentController::class, 'confirm']);
});
4.7 EnrolmentController: Orchestrator Lengkap
Controller ini merealisasikan sequence diagram dari Bagian 4. Pesan pada controller sesuai dengan pemanggilan method di bawah ini, sementara detail persistensi dienkapsulasi oleh service.
<?php
// app/Http/Controllers/EnrolmentController.php
namespace App\Http\Controllers;
use App\Exceptions\PaymentFailedException;
use App\Http\Requests\ConfirmEnrolmentRequest;
use App\Http\Requests\ShowEnrolmentSummaryRequest;
use App\Http\Resources\CourseResource;
use App\Http\Resources\EnrolmentResource;
use App\Models\Student;
use App\Services\CourseService;
use App\Services\EnrolmentService;
use App\Services\PaymentGateway;
use Illuminate\Http\JsonResponse;
class EnrolmentController extends Controller
{
public function __construct(
private CourseService $courseService,
private EnrolmentService $enrolmentService,
private PaymentGateway $paymentGateway,
) {
$this->middleware('auth');
}
/**
* Langkah 6 dari sequence diagram: tampilkan ringkasan pendaftaran.
* Mahasiswa telah memilih mata kuliah dan sistem memvalidasi
* prasyarat sebelum menampilkan layar konfirmasi.
*/
public function showSummary(ShowEnrolmentSummaryRequest $request): JsonResponse
{
$courseId = $request->integer('course_id');
$student = $this->getAuthenticatedStudent();
// Periksa kuota (Activity Diagram decision node: D3)
if (!$this->courseService->checkQuota($courseId)) {
return response()->json([
'error' => 'Mata kuliah penuh. Silakan gabung daftar tunggu.',
], 400);
}
// Periksa konflik jadwal (Activity Diagram decision node: D4)
$conflict = $this->enrolmentService->checkScheduleConflict(
$student->id, $courseId
);
if ($conflict['has_conflict']) {
return response()->json([
'error' => 'Konflik jadwal',
'conflict' => $conflict,
], 409);
}
// Hitung biaya dan buat ringkasan
$course = $this->courseService->getCourseDetails($courseId);
$fee = $this->courseService->calculateFee($courseId, $student->id);
return response()->json([
'course' => new CourseResource($course),
'fee' => $fee,
'message' => 'Silakan konfirmasi pendaftaran Anda.',
]);
}
/**
* Langkah 8-13 dari sequence diagram: konfirmasi pendaftaran,
* proses pembayaran, buat catatan, dan kembalikan sukses.
*/
public function confirm(ConfirmEnrolmentRequest $request): JsonResponse
{
$courseId = $request->integer('course_id');
$student = $this->getAuthenticatedStudent();
// Validasi ulang kuota dan konflik (proteksi race condition)
if (!$this->courseService->checkQuota($courseId)) {
return response()->json([
'error' => 'Mata kuliah sudah penuh.',
], 400);
}
$conflict = $this->enrolmentService->checkScheduleConflict(
$student->id, $courseId
);
if ($conflict['has_conflict']) {
return response()->json([
'error' => 'Konflik jadwal terdeteksi.',
'conflict' => $conflict,
], 409);
}
$fee = $this->courseService->calculateFee($courseId, $student->id);
// Proses pembayaran melalui gateway eksternal
try {
$paymentResult = $this->paymentGateway->charge(
$fee, $student->id, $courseId
);
} catch (PaymentFailedException $e) {
return response()->json([
'error' => 'Pembayaran gagal: ' . $e->getMessage(),
], 402);
}
// Buat pendaftaran (transaksional: enrolment + payment + quota)
$enrolment = $this->enrolmentService->createPaidEnrolment(
$student->id,
$courseId,
$paymentResult['transaction_id']
);
// Dalam aplikasi production, kirim notifikasi di sini.
// Respons mengonfirmasi bahwa jadwal dapat diperbarui dari enrolments.
return response()->json([
'message' => 'Pendaftaran berhasil.',
'enrolment' => new EnrolmentResource($enrolment),
], 201);
}
private function getAuthenticatedStudent(): Student
{
/** @var \App\Models\User $user */
$user = auth()->user();
return $user->student ?? throw new \RuntimeException(
'Hanya mahasiswa yang dapat mendaftar mata kuliah.'
);
}
}
API Routes:
<?php
// routes/api.php
use App\Http\Controllers\EnrolmentController;
Route::middleware('auth:sanctum')->group(function () {
Route::post('/enrolments/summary', [EnrolmentController::class, 'showSummary']);
Route::post('/enrolments/confirm', [EnrolmentController::class, 'confirm']);
});
5. Traceability: From Diagrams to Code
This five-part series has demonstrated end-to-end traceability: a core value of UML. The implementation snippets are intentionally compact, but each major code path can still be traced back to a diagram, and each diagram can be traced back to a line in the use case scenario.
| Source | Artefact | Implementation |
|---|---|---|
| Part 1: Use Case Diagram | ”Enrol in Course” oval | EnrolmentController class |
| Part 2: Use Case Scenario | Step 6: System displays enrolment summary | ShowEnrolmentSummaryRequest + showSummary() |
| Part 2: Use Case Scenario | Step 8–11: Payment + enrolment creation | ConfirmEnrolmentRequest + confirm() + createPaidEnrolment() |
| Part 2: Use Case Scenario | Step 12: Confirmation notification | Marked as a production dispatch point in confirm() |
| Part 2: Use Case Scenario | Step 13: Updated schedule | Returned as refreshable enrolment data; Schedule is modelled as a derived view |
| Part 2: Alt Flow D | Payment failed | PaymentFailedException + catch block |
| Part 3: Activity Diagram | Decision node “Course full?” | checkQuota() call in showSummary() |
| Part 3: Activity Diagram | Decision node “Schedule conflict?” | checkScheduleConflict() in showSummary() |
| Part 3: Activity Diagram | Transaction boundary after payment | DB::transaction() in createPaidEnrolment() |
| Part 4: Sequence Diagram | getCourseDetails(courseId) | CourseService::getCourseDetails() + CourseResource |
| Part 4: Sequence Diagram | checkScheduleConflict(studentId, courseId) | EnrolmentService::checkScheduleConflict() |
| Part 4: Sequence Diagram | charge(amount, studentId, courseId) | PaymentGateway::charge() |
| Part 4: Sequence Diagram | createPaidEnrolment(studentId, courseId, txId) | EnrolmentService::createPaidEnrolment() + EnrolmentResource |
| Part 4: Sequence Diagram | recordPayment(...) | Payment::create() inside the service transaction |
| Part 4: Sequence Diagram | incrementEnrolledCount(courseId) | Course::decrementQuota() |
| Part 5: Class Diagram | Student → Enrolment (1 to 0..*) | Student::enrolments() relationship |
| Part 5: Class Diagram | Student → Schedule (1 to 1) | Conceptual schedule view derived from enrolments |
| Part 5: Class Diagram | Enrolment → Payment (composition) | Enrolment::payment() + cascade delete |
| Part 5: Class Diagram | Form Request and Resource classes | Laravel validation and response transformation |
This traceability means that when a stakeholder asks “What happens if the course is full?”, you can point to the alternative flow in the scenario, trace it to the decision node in the activity diagram, and finally to the if (!checkQuota(...)) check in the controller. Nothing is lost in translation.
5. Ketertelusuran: Dari Diagram ke Kode
Seri lima bagian ini telah mendemonstrasikan ketertelusuran end-to-end: nilai inti dari UML. Cuplikan implementasi sengaja dibuat ringkas, tetapi setiap alur kode utama tetap dapat ditelusuri kembali ke diagram, dan setiap diagram dapat ditelusuri kembali ke baris dalam use case scenario.
| Sumber | Artefak | Implementasi |
|---|---|---|
| Bagian 1: Use Case Diagram | Oval “Daftar Mata Kuliah” | Kelas EnrolmentController |
| Bagian 2: Use Case Scenario | Langkah 6: Sistem menampilkan ringkasan | ShowEnrolmentSummaryRequest + method showSummary() |
| Bagian 2: Use Case Scenario | Langkah 8–11: Pembayaran + pembuatan pendaftaran | ConfirmEnrolmentRequest + method confirm() + createPaidEnrolment() |
| Bagian 2: Use Case Scenario | Langkah 12: Notifikasi konfirmasi | Ditandai sebagai titik dispatch production di confirm() |
| Bagian 2: Use Case Scenario | Langkah 13: Jadwal diperbarui | Dikembalikan sebagai data enrolment yang bisa di-refresh; Schedule dimodelkan sebagai view turunan |
| Bagian 2: Alt Flow D | Pembayaran gagal | PaymentFailedException + blok catch |
| Bagian 3: Activity Diagram | Decision node “Mata kuliah penuh?” | checkQuota() di showSummary() |
| Bagian 3: Activity Diagram | Decision node “Konflik jadwal?” | checkScheduleConflict() di showSummary() |
| Bagian 3: Activity Diagram | Batas transaksi setelah pembayaran | DB::transaction() di createPaidEnrolment() |
| Bagian 4: Sequence Diagram | getCourseDetails(courseId) | CourseService::getCourseDetails() + CourseResource |
| Bagian 4: Sequence Diagram | checkScheduleConflict(studentId, courseId) | EnrolmentService::checkScheduleConflict() |
| Bagian 4: Sequence Diagram | charge(amount, studentId, courseId) | PaymentGateway::charge() |
| Bagian 4: Sequence Diagram | createPaidEnrolment(studentId, courseId, txId) | EnrolmentService::createPaidEnrolment() + EnrolmentResource |
| Bagian 4: Sequence Diagram | recordPayment(...) | Payment::create() di dalam transaksi service |
| Bagian 4: Sequence Diagram | incrementEnrolledCount(courseId) | Course::decrementQuota() |
| Bagian 5: Class Diagram | Student → Enrolment (1 ke 0..*) | Relasi Student::enrolments() |
| Bagian 5: Class Diagram | Student → Schedule (1 ke 1) | View jadwal konseptual yang diturunkan dari enrolments |
| Bagian 5: Class Diagram | Enrolment → Payment (composition) | Enrolment::payment() + cascade delete |
| Bagian 5: Class Diagram | Class Form Request dan Resource | Validasi dan transformasi respons standar Laravel |
Ketertelusuran ini berarti bahwa ketika stakeholder bertanya “Apa yang terjadi jika mata kuliah penuh?”, Anda dapat menunjuk ke alur alternatif dalam skenario, menelusurinya ke decision node di activity diagram, dan akhirnya ke pengecekan if (!checkQuota(...)) di controller. Tidak ada yang hilang dalam prosesnya, dari diagram hingga ke kode.
6. Running the Application
To run the complete Campus Course Registration System:
# Clone and set up
git clone <your-repo> campus-registration
cd campus-registration
composer install
cp .env.example .env
php artisan key:generate
# Configure your database in .env, then:
php artisan migrate
php artisan db:seed # if you create seeders for test data
# Start the development server
php artisan serve
# Test the enrolment flow:
curl -X POST http://localhost:8000/api/enrolments/summary \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"course_id": 1}'
curl -X POST http://localhost:8000/api/enrolments/confirm \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"course_id": 1}'
6. Menjalankan Aplikasi
Untuk menjalankan Sistem Pendaftaran Mata Kuliah Kampus yang lengkap:
# Clone dan setup
git clone <repo-anda> campus-registration
cd campus-registration
composer install
cp .env.example .env
php artisan key:generate
# Konfigurasi database Anda di .env, lalu:
php artisan migrate
php artisan db:seed # jika Anda membuat seeder untuk data uji
# Mulai server development
php artisan serve
# Uji alur pendaftaran:
curl -X POST http://localhost:8000/api/enrolments/summary \
-H "Authorization: Bearer TOKEN_ANDA" \
-H "Content-Type: application/json" \
-d '{"course_id": 1}'
curl -X POST http://localhost:8000/api/enrolments/confirm \
-H "Authorization: Bearer TOKEN_ANDA" \
-H "Content-Type: application/json" \
-d '{"course_id": 1}'
7. Series Conclusion
Over five parts, we have:
- Part 1: Defined the system scope with a Use Case Diagram, identifying actors and their goals.
- Part 2: Specified the “Enrol in Course” use case in detail with a structured scenario, including preconditions, postconditions, main flow, and four alternative flows.
- Part 3: Visualised the business workflow as an Activity Diagram with decision nodes for every branching condition.
- Part 4: Modelled object-level interactions with a Sequence Diagram, defining precise method signatures, transactional boundaries, and external system communication.
- Part 5: Designed the static structure with a Class Diagram and implemented it as a working Laravel application with migrations, Eloquent models, services, and a controller.
This is the power of UML: each diagram answers a different question, and together they form a complete specification that eliminates ambiguity before coding begins. The traceability from stakeholder requirement through to running code ensures that what you build is what was intended.
What to Learn Next
- State Machine Diagram: Model the lifecycle of an Enrolment (pending → paid → confirmed → completed → dropped).
- Deployment Diagram: Show how the Laravel app, database, payment gateway, and web server are deployed across nodes.
- Communication Diagram: An alternative to Sequence Diagrams that emphasises object structure over time ordering.
- Design Patterns in UML: Express patterns like Observer, Strategy, and Factory as UML diagrams.
7. Kesimpulan Seri
Selama lima bagian, kita telah:
- Bagian 1: Mendefinisikan ruang lingkup sistem dengan Use Case Diagram, mengidentifikasi aktor dan tujuan mereka.
- Bagian 2: Menspesifikasikan use case “Daftar Mata Kuliah” secara detail dengan skenario terstruktur, termasuk prasyarat, pascasyarat, alur utama, dan empat alur alternatif.
- Bagian 3: Memvisualisasikan alur kerja bisnis sebagai Activity Diagram dengan decision node untuk setiap kondisi percabangan.
- Bagian 4: Memodelkan interaksi level objek dengan Sequence Diagram, mendefinisikan method signature yang tepat, batas transaksional, dan komunikasi sistem eksternal.
- Bagian 5: Mendesain struktur statis dengan Class Diagram dan mengimplementasikannya sebagai aplikasi Laravel yang berfungsi dengan migrations, Eloquent models, services, dan controller.
Inilah kekuatan UML: setiap diagram menjawab pertanyaan yang berbeda, dan bersama-sama mereka membentuk spesifikasi lengkap yang menghilangkan ambiguitas sebelum coding dimulai. Ketertelusuran dari persyaratan stakeholder hingga kode yang berjalan memastikan bahwa apa yang Anda bangun adalah apa yang dimaksudkan.
Apa yang Dipelajari Selanjutnya
- State Machine Diagram: Model siklus hidup Enrolment (tertunda → dibayar → dikonfirmasi → selesai → dibatalkan).
- Deployment Diagram: Tunjukkan bagaimana aplikasi Laravel, database, payment gateway, dan web server di-deploy di berbagai node.
- Communication Diagram: Alternatif untuk Sequence Diagram yang menekankan struktur objek daripada urutan waktu.
- Design Patterns dalam UML: Ekspresikan pola seperti Observer, Strategy, dan Factory sebagai diagram UML.
Related Content
UML Mini Series Part 3: Activity Diagram
Part 3 of the UML Mini Series. Learn what an Activity Diagram is, why it is essential for business process modelling, and how to draw one for the 'Enrol in Course' workflow: with swimlanes, decisions, and merge nodes in PlantUML.
UML Mini Series Part 1: Introduction to UML & Use Case Diagram
Part 1 of the UML Mini Series. Learn what UML is, why it matters, when and where to use it, and how to create a Use Case Diagram: with a complete PlantUML example for a Campus Course Registration System.
UML Mini Series Part 4: Sequence Diagram
Part 4 of the UML Mini Series. Learn what a Sequence Diagram is, why it reveals object interactions, and how to draw one for the 'Enrol in Course' payment and enrolment flow: with lifelines, messages, and activation boxes in PlantUML.
UML Mini Series Part 2: Use Case Scenario
Part 2 of the UML Mini Series. Learn what a Use Case Scenario is, why it bridges requirements and design, and how to write a full scenario for the 'Enrol in Course' use case: with structured table, main success flow, and alternative flows.
Microservices Architecture Fundamentals with PHP
A foundational guide to microservices architecture for PHP developers. Learn why and when to decompose a monolith, how to design service boundaries using domain-driven design, synchronous vs asynchronous communication patterns, database-per-service, and runnable PHP examples with Lumen/Slim — complete with contract tests.