This can be done with a monolithic application or with an API and a frontend in independent machines. The second is easier to maintain over time but the first is easier to put together to come out with a prototype.
Let's say that your simplest architecture is a webserver and a database that contains a shift table. Each time a shift is requested, a record is inserted into the database with an ID and a "pending" status. When it is time to call the shift, it is marked with a "published" status that reflects that it appears or should appear on the screen where the turn that touches atenter is published.
The application has at least 3 endpoints :
-
GET /operar_turnos
: Render a form for the operator to call the next turn
-
GET /visualizar_turno
render a screen with the current shift
-
GET /pedir_turno
renderiza a screen to request a new turn
Let's also say that the screen of the current turn applies an animation via jQuery when the turn changes.
To do it in PHP
Well, I would not do it in PHP. The logic of PHP is based on requests. Enter a request, pass all the related logic and optionally print something (originally PHP was just to print HTML). You could do it? Sure, you could, if the flow were:
- User requests a ticket - > request for creation - > inserted in BBDD
- Operator advances a turn in the queue - > request modification - > Updates the oldest turn in the "pending" state in the BBDD
- Shift screen is asking every 5 seconds or 1 second which is the most recent turn with "published" status. - > query request - > Reading in BBDD
For this to work you have to implement a other three endpoints:
-
POST /crear_turno
: receives the instruction to insert a turn with status "pending" in the database, returns the ID, is intended to be consumed by ajax
-
POST /actualizar_turno
: does not print anything, only receives the instruction to update a shift with "pending" status in the database to "published" status
-
GET /turno_actual
returns the current turn, is intended to be consumed by ajax
On the screen that asks for a turn:
<button id="crear_turno">Pedir Turno</button>
<script>
jQuery('#crear_turno').on('click',function() {
jQuery.ajax({
url:'/crear_turno'
type: 'POST'
}).then(function(turno_creado) {
// imprimo un ticket con el turno_creado
});
});
</script>
On the screen that updates a turn:
<button id="actualizar_turno">Actualizar Turno</button>
<script>
jQuery('#actualizar_turno').on('click',function() {
jQuery.ajax({
url:'/actualizar_turno'
type: 'POST'
});
});
</script>
On the screen that renders the current turn you would put:
<div id="turno_actual"></div>
<script>
var turno_en_pantalla;
function imprimeTurnoActual() {
jQuery.ajax({
url:'/turno_actual'
}).then(function(turno_actual) {
// necesito comparar con el turno actual y sólo
// aplico la animación si hubo un cambio
if(turno_actual !== turno_en_pantalla) {
turno_en_pantalla = turno_actual;
jQuery('#turno_actual')
.fadeOut(500)
.text(turno_actual)
.fadeIn(500);
}
});
}
setInterval(imprimeTurnoActual,1000); // cada 1 segundo
</script>
That may work, but it has a race condition
. If two operators move the turn at the same time and the consultation interval is very long, it may pass from turn 1 to turn 3 without showing that turn 2 is being called. It is also inefficient. If you ask every 0.1 seconds to mitigate the race condition, you are saturating the webserver and the bbdd. Each request opens a connection to the database and by the mere latency of connection between each layer a very short interval will fill the available connections. Let's also think that you have 5 screens showing the last turn. Each of them hits the database every 1 second.
How would you do it?
I would raise an application in nodejs using express + socket.io. Using the 3 endpoints at the beginning you have the operator screen, the screen to request a turn and the current turn screen.
But here comes the difference : you do not need the 3 additional endpoints designed to operate with ajax . Screens that render HTML establish a connection to the websockets server. This connection is persistent.
When the operator updates a turn you get the most recent turn ID and send a message to the connected clients (for what matters, the turn-based display screen). This screen has a listener that says: "if my connection to websocket sends me a message of type nuevo_turno
I check the payload of the message and so I know the new turn"
With this structure you do not need the endpoints to create or update shifts. You can make the screens to request or update turns issue an event pedir_turno
and actualizar_turno
respectively, and let the websockets server listen to that type of messages:
io.on('connection', function (socket) {
socket.on('pedir_turno', function () {
// ejecutas INSERT INTO turnos (estado, creacion) VALUES ('pendiente', now());
// y obtienes el último ID insertado asignándolo a la variable "turno_creado"
socket.broadcast.emit('turno_creado', turno_creado);
});
socket.on('actualizar_turno', function () {
// ejecutas: SELECT id from turnos WHERE estado='pendiente' ORDER BY creacion DESC limit 1;
// y obtienes el valor de la variable "turno_actual".
// ejecutas: UPDATE turnos set estado='publicado' WHERE id=turno_actual
socket.broadcast.emit('nuevo_turno', turno_actual);
});
});
On the screen that asks for a turn:
<button id="crear_turno">Pedir Turno</button>
<script>
var socket = io('http://localhost/');
socket.on('turno_creado', function(turno_creado) {
// imprimo un ticket con el turno_creado
});
jQuery('#crear_turno').on('click',function() {
socket.emit('crear_turno');
});
</script>
On the screen that updates a turn:
<button id="actualizar_turno">Actualizar Turno</button>
<script>
var socket = io('http://localhost/');
jQuery('#actualizar_turno').on('click',function() {
socket.emit('actualizar_turno');
});
</script>
On the screen that renders the current turn you would put:
<div id="turno_actual"></div>
<script>
var socket = io('http://localhost/');
socket.on('nuevo_turno', function (turno_actual) {
// no necesito comparar con el valor actual
// porque sólo ocurre cuando hubo un cambio
jQuery('#turno_actual')
.fadeOut(500)
.text(turno_actual)
.fadeIn(500);
});
</script>
And you save yourself from consulting an interval if your turn has changed. Here it is the same if you have 100 screens displaying the most recent turn. They all receive what the websockets server emits and they never hit the DB.
With this architecture you have only the 3 endpoints that render html and the rest you manage purely with websockets.