The proposal this post is understand how you make a simple microservices architecture, without all the drama that goes with it . We start with a point booster could be using for any context. Well, let do it
The first step were knows and is download a laravel repository, recommended from it's official page laravel official. Then will download docker(Im using docker desktop because is very easy for me) if you be using linux, must go to Docker official linux.
Once already with enviroment, were go to prepare our architecture! For this blog we are work with a restaurant request to cousine, thats basically through api-manager(client) we called all microservices that be cousine, grocery-store and recipes; this microservices will communicate among themselves and then return a response to api-manager, it handler all request from any client. Remember create a project(Microservice for each module)
Our architecture show like :
api-manager
cousine
grocery-store
recipes
The next step is download rabbitMq server, for communicate from local enviroment. You can get from or if you has downloaded docker(required) using:
docker run --name rabbitmq bitnami/rabbitmq:latest
Ok almost ready ... Once you get all prepared, its time of create our .yaml file for each project. Here leave how you must do it :
For api-manager and anothers services, we starting with create a docker-compose file with name:
docker-compose.yml
You show something like
Remember that it, must be in root of your service or microservice
Now, you must get a docker compose document as follows:
services:
db:
image: mysql:latest
container_name: mysql
ports:
- "3306:3306"
volumes:
- mysql-volumes:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: laravel
networks:
- app-network
laravel-app:
build:
context: ./docker/php
container_name: laravel-app
volumes:
- .:/home/source/main
working_dir: /home/source/main
networks:
- app-network
nginx:
build:
context: ./docker/nginx
container_name: todo-nginx
ports:
- "8000:80"
depends_on:
- laravel-app
volumes:
- .:/home/source/main
networks:
- app-network
volumes:
mysql-volumes:
driver: local
networks:
app-network:
driver: bridge
name: app-network
Likewise for each services. Here :
Cousine
services:
db:
image: mysql:latest
container_name: mysql-cousine
ports:
- "3307:3306"
volumes:
- mysql-volumes-cousine:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: laravel_cousine
networks:
- app-network
laravel-app:
build:
context: ./docker/php
container_name: micro-cousine
volumes:
- .:/home/source/main
working_dir: /home/source/main
networks:
- app-network
nginx:
build:
context: ./docker/nginx
container_name: todo-nginx-cousine
ports:
- "8001:80"
depends_on:
- laravel-app
volumes:
- .:/home/source/main
networks:
- app-network
volumes:
mysql-volumes-cousine:
driver: local
networks:
app-network:
driver: bridge
name: app-network
Recipes
services:
db:
image: mysql:latest
container_name: mysql-recipes
ports:
- "3309:3306"
volumes:
- mysql-volumes-recipes:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: laravel_recipes
networks:
- app-network
laravel-app:
build:
context: ./docker/php
container_name: micro-recipes
volumes:
- .:/home/source/main
working_dir: /home/source/main
networks:
- app-network
nginx:
build:
context: ./docker/nginx
container_name: todo-nginx-recipes
ports:
- "8003:80"
depends_on:
- laravel-app
volumes:
- .:/home/source/main
networks:
- app-network
volumes:
mysql-volumes-recipes:
driver: local
networks:
app-network:
driver: bridge
name: app-network
Grocery store
services:
db:
image: mysql:latest
container_name: mysql-grosery-store
ports:
- "3308:3306"
volumes:
- mysql-volumes-grosery-store:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: laravel_grocery
networks:
- app-network
laravel-app:
build:
context: ./docker/php
container_name: micro-grosery-store
volumes:
- .:/home/source/main
working_dir: /home/source/main
networks:
- app-network
nginx:
build:
context: ./docker/nginx
container_name: todo-nginx-grosery-store
ports:
- "8002:80"
depends_on:
- laravel-app
volumes:
- .:/home/source/main
networks:
- app-network
volumes:
mysql-volumes-grosery-store:
driver: local
networks:
app-network:
driver: bridge
name: app-network
Then for each services:
docker compose up
Remember that each microservice works with yours own databases settings, you must access to into console and set access for databases.
Important: You must set your databases into your container, allow grant access, create user etc.
In this part is moment lets to code and setting. In this case, api-manager handle all request from clients, then send this request to each microservice(cousine, grocery-store, recipes) some like this
<?php
namespace App\Http\Controllers;
use App\Jobs\Cousine\CreateCousineJob;
use App\Jobs\Cousine\DeleteCousineJob;
use App\Jobs\Cousine\FindOneCousineJob;
use App\Jobs\Cousine\GetCousineJob;
use App\Jobs\Cousine\UpdateCousineJob;
use App\Models\Cousine;
use App\Services\rabbitmq\ConsumeMQ;
use App\Services\rabbitmq\SendMQ;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
class CousineController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
$response = Http::get('http://nginx/api/cousine');
if ($response->successful()) {
return response()->json($response->json(), 200);
} else {
return response()->json(['error' => 'No se pudieron obtener los datos'], $response->status());
}
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
SendMQ::handle('send_store_cousine_queue','send_store_cousine_exchange','cousine_key', $request->all());
SendMQ::close();
return response()->json(['message' => 'Mensaje enviado correctamente']);
}
/**
* Display the specified resource.
*/
public function show(string $id)
{
$response = Http::get('http://nginx/api/cousine/'.$id);
if ($response->successful()) {
return response()->json($response->json(), 200);
} else {
return response()->json(['error' => 'No se pudieron obtener los datos'], $response->status());
}
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, string $id)
{
$payload = [
'order_number' => $request->order_number,
'status_id' => $request->status_id,
'id' => $id
];
// Despachar el job para actualizar una receta específica
SendMQ::handle('send_update_cousine_queue','send_update_cousine_exchange','cousine_key_update', $payload);
SendMQ::close();
}
public function updateWithIngredientsUpdated(Request $request, string $id)
{
$payload = [
'order_number' => $request->order_number,
'status_id' => $request->status_id,
'recipe_id' => $request->recipe_id,
'id' => $id
];
// Despachar el job para actualizar una receta específica
SendMQ::handle('send_update_cousine_ingredient_queue','send_update_cousine_ingredient_exchange','cousine_key_update_ingredient', $payload);
SendMQ::close();
}
/**
* Remove the specified resource from storage.
*/
public function destroy(string $id)
{
DeleteCousineJob::dispatch($id);
return response()->json(['message' => 'Cousine deleted'], 200);
}
}
Yes I know ... You question is ¿Where I get the SendMQ class?
In this example, I wanna show, how can use strategies when using microservices, doesn't necessary that you implement everything with rabbitMQ or Kafka or sqs, events directs. You can combine depends of you needed. For this example, I was create a service folder and with 2 services : ConsumeMQ and SendMQ, each one handle they responsabilities. Here leave a code:
SendMQ:class
<?php
namespace App\Services\rabbitmq;
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;
class SendMQ
{
protected static $connection;
protected static $channel;
/**
* Establece la conexión a RabbitMQ y declara el canal de conexión.
*
* Si ya existe una conexión, no hace nada.
*
* @return void
*/
protected static function connect()
{
if (is_null(self::$connection)) {
self::$connection = new AMQPStreamConnection(
env('AMQP_HOST', 'localhost'),
env('AMQP_PORT', 5672),
env('AMQP_USER', 'bryan'),
env('AMQP_PASSWORD', '123456'),
env('AMQP_VHOST', '/')
);
self::$channel = self::$connection->channel();
}
}
/**
* Envia un mensaje a la cola de mensajeria indicada.
*
* @param string $queueName Nombre de la cola de mensajeria.
* @param string $exchangeName Nombre de la exchange de mensajeria.
* @param string $routingKey Clave de routing para la cola especificada.
* @param mixed $data Contenido del mensaje.
*/
public static function handle($queueName, $exchangeName, $routingKey, $data)
{
self::connect();
self::$channel->exchange_declare($exchangeName, 'direct', false, false, false);
self::$channel->queue_declare($queueName, false, false, false, false);
self::$channel->queue_bind($queueName, $exchangeName, $routingKey);
$msg = new AMQPMessage(json_encode($data));
self::$channel->basic_publish($msg, $exchangeName, $routingKey);
}
/**
* Cierra la conexi n a RabbitMQ.
*
* Este m todo debe ser llamado cuando se haya terminado
* de enviar mensajes y se desee liberar los recursos
* relacionados con la conexi n.
*/
public static function close()
{
if (self::$channel) {
self::$channel->close();
}
if (self::$connection) {
self::$connection->close();
}
}
}
ConsumeMQ
<?php
namespace App\Services\rabbitmq;
use App\Http\Controllers\CousineController;
use Illuminate\Support\Facades\Log;
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;
class ConsumeMQ
{
protected static $connection;
protected static $channel;
protected static $receivedData = []; // Variable estática para almacenar datos
protected static function connect()
{
if (is_null(self::$connection)) {
self::$connection = new AMQPStreamConnection(
env('AMQP_HOST', 'localhost'),
env('AMQP_PORT', 5672),
env('AMQP_USER', 'bryan'),
env('AMQP_PASSWORD', '123456'),
env('AMQP_VHOST', '/')
);
self::$channel = self::$connection->channel();
}
}
public static function handle($queueName)
{
self::connect();
// Declarar la cola
self::$channel->queue_declare($queueName, false, false, false, false);
// Callback para manejar los mensajes recibidos
$msgCallback = function (AMQPMessage $msg) {
Log::info(' [x] Recibiendo: ' . $msg->body);
// Almacenar el mensaje recibido
$list = json_decode($msg->body, true);
CousineController::setDataList($list);
};
// Configurar el consumidor
self::$channel->basic_consume($queueName, '', false, true, false, false, $msgCallback);
// Mantener el consumidor activo hasta que se detenga
echo " [*] Waiting for messages. To exit press CTRL+C\n";
while (self::$channel->is_consuming()) {
self::$channel->wait();
}
return self::$receivedData; // Retornar los datos recibidos
}
public static function getReceivedData()
{
return self::$receivedData;
}
public static function close()
{
if (self::$channel) {
self::$channel->close();
}
if (self::$connection) {
self::$connection->close();
}
}
}
then let to configure our exchange. On *config * folder you must create a file with a name that identify you connection with rabbitmq. For this case : amqp.php. The context is next
return [
/*
|--------------------------------------------------------------------------
| Define which configuration should be used
|--------------------------------------------------------------------------
*/
'use' => env('AMQP_ENV', 'production'),
/*
|--------------------------------------------------------------------------
| AMQP properties separated by key
|--------------------------------------------------------------------------
*/
'properties' => [
'production' => [
'host' => env('AMQP_HOST', 'localhost'),
'port' => env('AMQP_PORT', 5672),
'username' => env('AMQP_USER', 'myuser'),
'password' => env('AMQP_PASSWORD', 'mypass'),
'vhost' => env('AMQP_VHOST', '/'),
'connect_options' => [],
'ssl_options' => [],
'exchange' => 'amq.topic',
'exchange_type' => 'topic',
'exchange_passive' => false,
'exchange_durable' => true,
'exchange_auto_delete' => false,
'exchange_internal' => false,
'exchange_nowait' => false,
'exchange_properties' => [],
'queue_force_declare' => false,
'queue_passive' => false,
'queue_durable' => true,
'queue_exclusive' => false,
'queue_auto_delete' => false,
'queue_nowait' => false,
'queue_properties' => ['x-ha-policy' => ['S', 'all']],
'consumer_tag' => '',
'consumer_no_local' => false,
'consumer_no_ack' => false,
'consumer_exclusive' => false,
'consumer_nowait' => false,
'consumer_properties' => [],
'timeout' => 0,
'persistent' => false,
'publish_timeout' => 0, // Only applicable when a publish is marked as mandatory
'qos' => false,
'qos_prefetch_size' => 0,
'qos_prefetch_count' => 1,
'qos_a_global' => false
],
],
];
With this, everything, we are already for work...
*I wait that being usefull for you .. remember leave you comment or ask. You always are welcome *
Fullcode link Here
Follow me : @brngranado at Instagram, X, LinkedIn, threads.
Top comments (0)