- Notifications
You must be signed in to change notification settings - Fork11
Demo for DriftPHP
driftphp/demo
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
This is a simple Demo about how to use properly DriftPHP. Please, read carefullyourDocumentation in order to understand the rationalebehind each functionality. Built on top ofPHP8.
Remember. This is a demo package. We encourage to make us PRs if you find somebugs, but we're not going to add new features unless is necessary.
This demo uses these DriftPHP packages
- Server
- Http Kernel
- Command Bus + AMQP Adapter
- Event Bus + AMQP Adapter
- Websocket
- Twig Adapter
- DBAL
This demo supports PHP8. We only made 2 changes to make this happen. First ofall, new DriftPHP projects that want to change to PHP8 should start using thephp8 Docker image - driftphp/base-php8 -. After that, and because somedependencies don't have their updates to accept the new PHP version, youshould install/update your composer dependencies with the flag
--ignore-platform-reqs
. With these 2 changes, we want to welcome PHP8 with abig smile on our faces :)
Did you enjoy this demo? Do you think that this DriftPHP Framework deserves anopportunity inside the ecosystem? Then easy. You can help us by making somesmall actions
- Make a tweet with hashtag #driftPHP. Tell the world what did you see here
- ⭐ our used components by doing just
composer thanks
You can join our small community inDriftPHP in Gitter if you have anyissue or question around this project. We will be very happy to say hello :)
This project is a sample for integrating Symfony and ReactPHP. The base of theproject is a simple and clean DriftPHP installation usingdrift/skeleton
, sothat means that, if your code is built on top of that structure, you're a littlebit nearer than turning your project non-blocking.
In this case, we have build an HTTP key/value service, allowing us to storestring values under some keys, get that values and delete them. As simple as itsounds. We will use Twig as well to render some small templates with some ofthis stored information.
/ GET/values/{key} GET/values/{key} PUT/values/{key} DELETE
We could have implemented the feature by using a regular DriftPHP installation,by installing an Nginx and by using one of the existing Redis solutions for PHP.That would have been the fastest and simplest solution, but we want a little bitmore.
- We want simplicity. No more Nginx for a simple server
- We want performance. Each ms matters. Really.
- We want a non-blocking implementation. If we have one long query, we don'twant the server to be blocked
These are the prerequisites for that project, and by using this newrepositories, we can have everything you need here in one single server.
Let's check how.
You can start using this demo by usingdocker-compose
. As easy as it sounds.
./docker-compose-build.sh
Once the whole environment is up and running, you'll be able to request theserver by accessing the port8999
through your browser.
http://127.0.0.1:8999/
You can change the forwarded port by changing the values from the .env file
SERVER_PORT=8999WEBSOCKET_PORT=1234
You might encounter some troubles. Since this docker-compose definition fileworks with some external images, some pieces could not work as expected due tosome cache problems. Make sure that you run this demo from the scratch and notfrom cached containers and volumes.
Please. Keep reading a bit more in order to understand how this application isdesigned in terms of architecture. First of all, let's take a look at ourdesired achievements.
- We basically want a service that saves key-value pairs in a persistentdatabase.
- We want to expose a basic API Rest (PUT, DELETE, GET) in order to be able tointeract with this persistence layer.
- We assume that the whole key-value data-set will be small enough to be storedin memory, so we will design out application on top of events. Read from memory,write to persistence layer.
- We want to expose a websocket where we will send some events. For now, eachtime a key is added/updated or deleted, we will create a new domain event.
- Other websocket connections and disconnections will produce events as well.
- We want a landing page with real-time updated key-value database and domainevents.
Before continuing, take the next 2 minutes thinking how would you implement thisarchitecture using Laravel or Symfony. First of all, in these frameworks yousimply can't use memory as a local storage, so each read means a persistencehit (yes, Redis is persistence as well). Working with a distributed set ofservices and websockets would need external software pieces in order to work,basically because Symfony (and all other frameworks) can't do more than onething at the same time.
Let's try with DriftPHP. In order to be able to do that, we will work with allthese containers, using the same networks and interacting among each other.
RabbitMQ as a queues and exchanges system
PostgreSQL as a persistence layer used as a driver in our DBAL configuration
Tiny balancer, listening at port 8999 and balancing among next servers
Server1 - DriftPHP server serving HTTP requests and subscribed to a temporary.Configured to Enqueue commands asynchronouslyexchange for domain events
Server2 - Same than Server1
Server3 - Same than Server1
Websocket - Exposes one websocket route in
/events
and dispatches all domainevents in a documented format
So let's take a look at each action
You can put a value using the exposed api REST.
curl -i -XPUT -H'Content-Type: application/json' localhost:8999/values/mykey -d'myvalue'HTTP/1.1 200 OKcache-control: no-cache, privatedate: Thu, 09 May 2019 18:44:03 GMTcontent-type: application/jsonX-Powered-By: React/alphaContent-Length: 48Connection: close{"key":"mykey","value":"myvalue","message":"OK"}
As you can see, you're using the port8999
, what means that the balancer willforward your request to one of the existing servers. This server will handle therequest and will execute a new Command through the command bus. Because thecommand bus is configured to work asynchronously, instead of handling thecommand, a middleware will enqueue it in a infrastructure queue (RabbitMQ usingAMQP protocol).
The command consumer will consume the command and will execute again the commandthrough the command bus, but in this case in the inline command bus (this onejust avoid the async middleware). That means that, in this case, the handlerwill handle the command and will update our persistence layer (PostgreSQL) withthe new value.
After that, the handler will create a new domain event and this one will bedispatched using the event bus. That means that all other network servicessubscribed to this queue will receive this domain event.
In fact, our 3 servers and the websocket will receive this domain event,allowing them all to reload a fresh copy of our data in memory (all key-values).Each websocket connection will receive as well an update related to this domainevent, updating the local and displayed lists.
In that case, we want to delete one of our values.
curl -i -XDELETE localhost:8999/values/mykeyHTTP/1.1 200 OKcache-control: no-cache, privatedate: Thu, 09 May 2019 18:47:03 GMTcontent-type: application/jsonX-Powered-By: React/alphaContent-Length: 29Connection: close{"key":"mykey","message":"1"}
The workflow is exactly the same one than thePUT action. Both work exactlythe same way.
We can use the api REST to request one key's value. If the key exists then wewill have the value.
curl -i -XGET localhost:8999/values/mykeyHTTP/1.1 200 OKcache-control: no-cache, privatedate: Thu, 09 May 2019 18:45:17 GMTcontent-type: application/jsonX-Powered-By: React/alphaContent-Length: 33Connection: close{"key":"mykey","value":"myvalue"}
Otherwise, we will have a 404.
curl -i -XGET localhost:8999/values/nonexistingkeyHTTP/1.1 404 Not Foundcache-control: no-cache, privatedate: Sat, 25 Jan 2020 17:23:33 GMTcontent-type: text/html; charset=UTF-8X-Powered-By: React/alphaContent-Length: 0Connection: close
Thanks to last workflows, reading a value is as simple as you will see here.Our request is handled by one of our servers (remember, the balancer forwardsit to one of our defined servers). The server creates a new Query objectrequesting a value given a key, and the handler uses the configured repository.
Repositories save an updated list of key-values, keeping it fresh by using thedomain events received through the event subscriber, so we could simply acceptthat yes, these values are properly updated!
Just check if the key is in the local array and return the value.That's it. Without any persistence layer access, without having bottlenecks.
You can open your browser tohttp://127.0.0.1:8999
and you will be see 2lists. The first one will be the current state of the website. These values arenot taken from the persistence layer, but from one fresh copy allocated in oneof our servers memory.
The other list is real-time updated list of domain events and websocket eventsfrom our system. These are the current events notified here.
- A new key-value has been put
- A key has been deleted
- A connection has been connected to the route /events
- A connection has been disconnected from the route /events
As soon as our persistence layer changes, both lists will change (the first oneby applying some deltas, the second by appending new domain events).
You can open multiple times this endpoint at the same time and play with theconcurrency.