@@ -15,7 +15,7 @@ First we define the events that happen in our system.
1515A hotel can be created with a ` name ` and an ` id ` :
1616
1717``` php
18- namespace App\Event ;
18+ namespace App\Events ;
1919
2020use Patchlevel\EventSourcing\Aggregate\Uuid;
2121use Patchlevel\EventSourcing\Attribute\Event;
@@ -33,7 +33,7 @@ final class HotelCreated
3333A guest can check in by ` name ` :
3434
3535``` php
36- namespace App\Event ;
36+ namespace App\Events ;
3737
3838use Patchlevel\EventSourcing\Attribute\Event;
3939
@@ -49,7 +49,7 @@ final class GuestIsCheckedIn
4949And also check out again:
5050
5151``` php
52- namespace App\Event ;
52+ namespace App\Events ;
5353
5454use Patchlevel\EventSourcing\Attribute\Event;
5555
@@ -75,11 +75,11 @@ In these methods the business checks are made and the events are recorded.
7575Last but not least, we need the associated apply methods to change the state.
7676
7777``` php
78- namespace App\Model ;
78+ namespace App\Models ;
7979
80- use App\Event \GuestIsCheckedIn;
81- use App\Event \GuestIsCheckedOut;
82- use App\Event \HotelCreated;
80+ use App\Events \GuestIsCheckedIn;
81+ use App\Events \GuestIsCheckedOut;
82+ use App\Events \HotelCreated;
8383use Patchlevel\EventSourcing\Aggregate\Uuid;
8484use Patchlevel\EventSourcing\Attribute\Aggregate;
8585use Patchlevel\EventSourcing\Attribute\Apply;
@@ -89,6 +89,7 @@ use Patchlevel\LaravelEventSourcing\AggregateRoot;
8989use function array_filter;
9090use function array_values;
9191use function in_array;
92+ use function sprintf;
9293
9394#[Aggregate(name: 'hotel')]
9495final class Hotel extends AggregateRoot
@@ -121,7 +122,7 @@ final class Hotel extends AggregateRoot
121122 public function checkIn(string $guestName): void
122123 {
123124 if (in_array($guestName, $this->guests, true)) {
124- throw new GuestHasAlreadyCheckedIn( $guestName);
125+ throw new RuntimeException(sprintf('Guest %s is already checked in', $guestName) );
125126 }
126127
127128 $this->recordThat(new GuestIsCheckedIn($guestName));
@@ -130,7 +131,7 @@ final class Hotel extends AggregateRoot
130131 public function checkOut(string $guestName): void
131132 {
132133 if (!in_array($guestName, $this->guests, true)) {
133- throw new IsNotAGuest( $guestName);
134+ throw new RuntimeException(sprintf('Guest %s is not checked in', $guestName) );
134135 }
135136
136137 $this->recordThat(new GuestIsCheckedOut($guestName));
@@ -175,12 +176,13 @@ Each projector is then responsible for a specific projection.
175176``` php
176177namespace App\Subscribers;
177178
178- use App\Event \GuestIsCheckedIn;
179- use App\Event \GuestIsCheckedOut;
180- use App\Event \HotelCreated;
179+ use App\Events \GuestIsCheckedIn;
180+ use App\Events \GuestIsCheckedOut;
181+ use App\Events \HotelCreated;
181182use Illuminate\Database\Schema\Blueprint;
182183use Illuminate\Support\Facades\DB;
183184use Illuminate\Support\Facades\Schema;
185+ use Patchlevel\EventSourcing\Aggregate\Uuid;
184186use Patchlevel\EventSourcing\Attribute\Projector;
185187use Patchlevel\EventSourcing\Attribute\Setup;
186188use Patchlevel\EventSourcing\Attribute\Subscribe;
@@ -195,7 +197,7 @@ final class HotelProjection
195197 /** @return list<array {id: string, name: string, guests: int} > */
196198 public function getHotels(): array
197199 {
198- DB::select('select id, name, guests from ' . $this->table());
200+ return DB::select('select id, name, guests from ' . $this->table());
199201 }
200202
201203 #[Subscribe(HotelCreated::class)]
@@ -205,7 +207,7 @@ final class HotelProjection
205207 "insert into {$this->table()} (id, name, guests) values (?, ?, ?)",
206208 [
207209 $event->id->toString(),
208- $event->name ,
210+ $event->hotelName ,
209211 0,
210212 ],
211213 );
@@ -251,18 +253,17 @@ final class HotelProjection
251253 }
252254}
253255```
254-
255256You need to register the projector in the ` event-sourcing.php ` configuration file.
256257
257258``` php
259+ use App\Subscribers\HotelProjection;
260+
258261return [
259262 'subscribers' => [
260- App\Subscribers\HotelProjector ::class,
263+ HotelProjection ::class,
261264 ],
262265];
263-
264266```
265-
266267!!! note
267268
268269 You can find out more about projections in the [library](https://event-sourcing.patchlevel.io/latest/subscription/).
@@ -274,47 +275,55 @@ In our example we also want to send an email to the head office as soon as a gue
274275``` php
275276namespace App\Subscribers;
276277
277- use App\Event\GuestIsCheckedIn;
278+ use App\Events\GuestIsCheckedIn;
279+ use Illuminate\Mail\Message;
278280use Illuminate\Support\Facades\Mail;
279281use Patchlevel\EventSourcing\Attribute\Processor;
282+ use Patchlevel\EventSourcing\Attribute\Subscribe;
283+
284+ use function sprintf;
280285
281286#[Processor('admin_emails')]
282287final class SendCheckInEmailProcessor
283288{
284289 #[Subscribe(GuestIsCheckedIn::class)]
285290 public function onGuestIsCheckedIn(GuestIsCheckedIn $event): void
286291 {
287- Mail::to('hq@patchlevel.de')->send(new GuestCheckedIn($event->guestName));
292+ Mail::raw('Event Sourcing is amazing!', static function (Message $message) use ($event): void {
293+ $message
294+ ->subject(sprintf('Guest %s checked in', $event->guestName))
295+ ->to('info@patchlevel.de');
296+ });
288297 }
289298}
290299```
291-
292300You need to register the processor in the ` event-sourcing.php ` configuration file.
293301
294302``` php
303+ use App\Subscribers\SendCheckInEmailProcessor;
304+
295305return [
296306 'subscribers' => [
297- App\Subscribers\ SendCheckInEmailProcessor::class,
307+ SendCheckInEmailProcessor::class,
298308 ],
299309];
300-
301310```
302-
303311!!! note
304312
305313 You can find out more about processor in the [library](https://event-sourcing.patchlevel.io/latest/subscription/)
306314
307315## Usage
308316
309- We are now ready to use the Event Sourcing System. We can load, change and save aggregates.
317+ We are now ready to use the Event Sourcing System.
318+ To demonstrate this, we create a controller that allows us to create hotels and check in and out guests.
310319
311320``` php
312- namespace App\Hotel\Infrastructure\Controller ;
321+ namespace App\Http\Controllers ;
313322
314- use App\Model \Hotel;
323+ use App\Models \Hotel;
315324use App\Subscribers\HotelProjection;
325+ use Illuminate\Http\JsonResponse;
316326use Illuminate\Http\Request;
317- use Illuminate\Http\Response;
318327use Patchlevel\EventSourcing\Aggregate\Uuid;
319328
320329use function response;
@@ -326,47 +335,63 @@ final class HotelController
326335 ) {
327336 }
328337
329- public function listAction (): Response
338+ public function list (): JsonResponse
330339 {
331340 return response()->json(
332341 $this->hotelProjection->getHotels(),
333342 );
334343 }
335344
336- public function createAction (Request $request): Response
345+ public function create (Request $request): JsonResponse
337346 {
338- $hotelName = $request->request->get('name'); // need validation!
339- $id = Uuid::v7();
347+ $hotelName = $request->json('name'); // need validation!
348+
349+ $id = Uuid::generate();
340350
341351 $hotel = Hotel::create($id, $hotelName);
342352 $hotel->save();
343353
344354 return response()->json(['id' => $id->toString()]);
345355 }
346356
347- public function checkInAction(Uuid $hotelId , Request $request): Response
357+ public function checkIn(string $id , Request $request): JsonResponse
348358 {
349359 $guestName = $request->request->get('name'); // need validation!
350360
351- $hotel = Hotel::load($hotelId );
361+ $hotel = Hotel::load(Uuid::fromString($id) );
352362 $hotel->checkIn($guestName);
353363 $hotel->save();
354364
355365 return response()->json();
356366 }
357367
358- public function checkOutAction(Uuid $hotelId , Request $request): Response
368+ public function checkOut(string $id , Request $request): JsonResponse
359369 {
360370 $guestName = $request->request->get('name'); // need validation!
361371
362- $hotel = Hotel::load($hotelId );
372+ $hotel = Hotel::load(Uuid::fromString($id) );
363373 $hotel->checkOut($guestName);
364374 $hotel->save();
365375
366376 return response()->json();
367377 }
368378}
369379```
380+ The last step is to define the routes in the ` routes/api.php ` file.
381+
382+ ``` php
383+ use App\Http\Controllers\HotelController;
384+ use Illuminate\Support\Facades\Route;
385+
386+ Route::get('/hotel', [HotelController::class, 'list']);
387+ Route::post('/hotel/create', [HotelController::class, 'create']);
388+ Route::post('/hotel/{id}/check-in', [HotelController::class, 'checkIn']);
389+ Route::post('/hotel/{id}/check-out', [HotelController::class, 'checkOut']);
390+ ```
391+ !!! warning
392+
393+ Don't forget to define the path to the [api routes](https://laravel.com/docs/11.x/routing#api-routes) in the `bootstrap/app.php` configuration file.
394+
370395## Result
371396
372397!!! success
0 commit comments