Skip to content

Simple cli application to build integrations during Integration Workshop

License

OSL-3.0, Unknown licenses found

Licenses found

OSL-3.0
LICENSE.txt
Unknown
COPYING.txt
Notifications You must be signed in to change notification settings

redboxmarcins/workshop-app

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

28 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Integration Workshop

Welcome to Magento Commerce Order Management workshop! In this repository you will find a simple cli application which will be used to build integrations with Magento Commerce Order Management.

The cli application usees SQLite to keep things simple. This way it's possible to run the program on your host machine without need to run local web server, database server, docker or virtual machine. But, of course, you can build integrations in any kind of application, and even in language other than PHP.

The application build on top of Silex microframework, Symfony Console component and Doctrine ORM. These allow us to provide thin, but yet powerful, platform for building an integration.

Although, keep in mind, this stack was chosen only for purpose of this workshop, we encourage you to choose tools and framework you feel more comfortable with when building integrations.

Requirements

In order to run application you would need to have:

Getting started

Clone this repository and run composer install. It will pull some 3rd party libraries required by the application.

$ composer install

Configure your AMQP connection

$ open app/config/parameters.yml # and set options in amqp section

Create a database file

$ app/console orm:schema-tool:create

You all set, now you can start building an integration!

Quick reference

Explore the application

We've tried to minimize application and hide implementation details, so you don't need to learn how components work, just write your code in certain places and you should be good to go!

Directory Structure

  • app - application files
    • config - configuration
    • console - cli entry point
  • src - source files
  • tests - tests
  • vendor - 3rd party libraries installed through composer

Database Schema

We've pre-created few entities which should be useful for building warehouse integration. Each entity represented by class, and repository which allows to persist and query entities.

  • Sku - an entity representing stock keeping unit
  • Stock - an entity representing stock level for a given SKU
  • ShipmentRequest - an entity representing a shipment request coming from MCOM
  • ShipmentRequestLine - an entity which represents a single line of the shipment request

You can use any SQLite client to review and modify content of the database. But in case you don't have any we've created a few simple commands which prints all records from database:

$ app/console query:shipmentrequest:all # Prints all shipment requests
$ app/console query:shipmentrequestline:all # Prints all shipment request lines
$ app/console query:sku:all # Prints all sku
$ app/console query:stock:all # Prints all stock levels

Extend the application

Adding a command

During this workshop you will be asekd to build additional commands to perform additional actions. Commands in console application are similar to controllers in normal web application. Each command has a name and arguments, you declare these per command. You can run command using cli entry point - app/console.

1. Create a command class

Simply create a new PHP class inside of the src/Command folder and extend it from ContainerAwareCommand. Make sure the namespace is Magento\Bootstrap\Command.

2. Override configure method

Create configure method with the same signature as in the parent class and set command name and list of arguments in the body of the method. For example:

protected function configure()
{
    $this->setName('update-stock-level') // name will be used to run your command
        ->setDescription('Send stock level update') // description will be shown when you run app/console without arguments
        ->addArgument('sku', InputArgument::REQUIRED, 'SKU') // you can add as many arguments as you need
        ->addArgument('qty', InputArgument::REQUIRED, 'Quantity')
    ;
}

3. Declare execute method

Last step, you need to implement logic of your command. You would need to override execute method and implement your logic inside. For exmaple, this command will publish a message to the API client:

protected function execute(InputInterface $input, OutputInterface $output)
{
    $this->getApiClient()->discover('oms')
        ->publish(new Request('magento.inventory.source_stock_management.update', '1', [
            'stock' => [
                'sku' => $input->getArgument('sku'),
                'quantity' => $input->getArgument('qty'),
                'source_id' => 'P_STORE_1',
                'type' => 'GOOD',
            ],
        ]));
}

You can access arguments declared in the step 2 using $input->getArgument('argument_name') method.

4. Run your command

To run your command simply type app/console and name of your command. Example above you can run like this:

$ app/console update-stock-level WB023115 14

Making API calls, publishing and broadcasting messages

This application uses the message-bus-client library, which provides very basic transport bindings for Magento Shared Specification.

Keep in mind: It's not an official library, just a basic implementation for purpose of this workshop.

You can retrieve API client using ContainerAwareCommand::getApiClient method.

Broadcast

You can broadcast message to all consumers using ClientInterface::broadcast method.

$this->getApiClient()
    ->boradcast(new Request(
        'message-topic',
        '1', // version
        [
            'argument_foo' => [1, 2, 3],
            'argument_bar' => 'ten',
        ]
    ));

Publish

You can publish a message to one particular service using the ClientInterface::discover method to choose the service, and then EndpointInterface::publish to actually publish a message.

$this->getApiClient()
    ->discover('oms')
    ->publish(new Request(
        'message-topic',
        '1', // version
        [
            'argument_foo' => [1, 2, 3],
            'argument_bar' => 'ten',
        ]
    ))

RPC Call

You can make a synchronous call to particular service using ClientInterface::discover method to choose the service, and then EndpointInterface::call to actually perform a call. This method call will return you a promise which then can be resolved into Response.

$promise = $this->getApiClient()
    ->discover('oms')
    ->call(new Request(
        'message-topic',
        '1', // version
        [
            'argument_foo' => [1, 2, 3],
            'argument_bar' => 'ten',
        ]
    ));

$response = $promise->resolve($timeout);

var_export($response->getValue());

Advanced details

  • API client is defined in Magento\Bootstrap\DependencyInjection\Provider\ApiProvider, you can tune it up, if needed.
  • More examples for client library can be found at Github.

Consuming messages

In previous section you learn how to broadcast and send messages to other services. Now it's time to see how you can receive messages from other services!

First, you need to create bindings for topics your application is interested in. Your application may implement one or multiple services defined in Magento Shared Service Specification. Implementing a service means being able to process all command, queries and dispatch all events defined in specification. Apart of that your application also can subscribe to any event exposed by other services.

You can subscribe to topics using API of message-bus-client library. You already learn how to discover remote endpoint, but as well you can define your own services using ClientInterface::define method.

$service = $client->define('my-service');

Now, you can create bindings for topics you are interested in by calling ServiceInterface::bind method. It accepts instance of the BindingInterface interface as an argument. You can create your own implementation which will work best for you, but for quick start you can use CallbackBinding. It allows you to bind any callable to the topic using CallbackBinding::on method.

$service->bind(
    (new CallbackBinding())
        ->on('message-topic', '0', function(Request $request) {
            // for sync methods you can return Response
            return new Response('result');
        })
        ->on('some-other-topic', '0', function(Request $request) {
            // for async methods you don't need to return anything
        })
);

These bindings can be defined in src/DependencyInjection/Provider/ServiceProvider.php. This file already have service defined, all you need to do is to add your callbacks.

Once bindings are done you can use service in AMQP or HTTP consumer.

AMQP consumer

There is already an AMQP consumer in the application, so you don't need to worry about a thing. You can start consuming messages by running:

$ app/console consume

Declaring your own entities

We decided to use Doctrine ORM in the application because it nicely hides all complexity of persistance layer providing nice object-level API. There are few entities defined in src/Model/Entity, you can use them as example to define your owns.

1. Create entity and define mapping

Create a new class in src/Model/Entity and define properties of your entity.

<?php

namespace Magento\Bootstrap\Model\Entity;

class Sku
{
    private $sku;
    private $name;

    // Define __construct, getters and setters depending on mutability of each field
}

Once you decide what fields your model will have, add Doctrine Annotations to explain what SQL types doctrine should use in the database.

<?php
namespace Magento\Bootstrap\Model\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass="\Magento\Bootstrap\Model\Repository\SkuRepository")
 * @ORM\Table(name="sku")
 */
class Sku
{
    /**
     * @ORM\Id                  -- exactly one of the fields should be marked as primary key
     * @ORM\Column(length=128)
     */
    private $sku;

    /**
     * @ORM\Column(length=128)
     */
    private $name;
    
    // ...
}

Read more about annotations in Doctrine documentation.

2. Update database schema

So far you have a data object for your entity, and you explained to doctrine how it should be stored. Last thing left in the list is to create space where entity will be stored. Lucky for us Doctrine has few commands which allow to create required tables in the database.

$ app/console orm:schema:update --force

About

Simple cli application to build integrations during Integration Workshop

Resources

License

OSL-3.0, Unknown licenses found

Licenses found

OSL-3.0
LICENSE.txt
Unknown
COPYING.txt

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages