From 5fa443c7b0689a64cecfa0ed9bc8c9c7def66732 Mon Sep 17 00:00:00 2001 From: Vincent Composieux Date: Thu, 1 Jun 2017 13:59:03 +0200 Subject: [PATCH] Updated articles to markdown --- _authors/vcomposieux.md | 6 + ...7-19-behat-structure-functional-tests.html | 293 -------------- ...hat-structurez-vos-tests-fonctionnels.html | 291 -------------- ...iser-le-composant-workflow-de-symfony.html | 159 -------- ...2016-09-29-symfony-workflow-component.html | 160 -------- ...creer-votre-premier-package-pour-atom.html | 286 -------------- _drafts/2016-12-05-create-atom-package.html | 267 ------------- ...dux-structurez-vos-applications-front.html | 212 ---------- ...redux-structure-frontend-applications.html | 196 ---------- ...service-discovery-failure-detection-2.html | 206 ---------- ...l-service-discovery-failure-detection.html | 193 --------- ...-07-19-behat-structure-functional-tests.md | 356 +++++++++++++++++ ...behat-structurez-vos-tests-fonctionnels.md | 370 ++++++++++++++++++ ...iliser-le-composant-workflow-de-symfony.md | 193 +++++++++ .../2016-09-29-symfony-workflow-component.md | 194 +++++++++ ...1-creer-votre-premier-package-pour-atom.md | 269 +++++++++++++ _posts/2016-12-05-create-atom-package.md | 268 +++++++++++++ ...redux-structurez-vos-applications-front.md | 254 ++++++++++++ ...0-redux-structure-frontend-applications.md | 254 ++++++++++++ ...l-service-discovery-failure-detection-2.md | 285 ++++++++++++++ ...sul-service-discovery-failure-detection.md | 287 ++++++++++++++ ...http2-nest-pas-le-futur-cest-le-present.md | 166 ++++++++ _posts/2017-04-12-http2-future-present.md | 167 ++++++++ ...2017-05-09-retours-sur-la-dotscale-2017.md | 194 +++++++++ img/authors/vcomposieux.jpg | Bin 0 -> 213403 bytes 25 files changed, 3263 insertions(+), 2263 deletions(-) create mode 100644 _authors/vcomposieux.md delete mode 100644 _drafts/2016-07-19-behat-structure-functional-tests.html delete mode 100644 _drafts/2016-07-19-behat-structurez-vos-tests-fonctionnels.html delete mode 100644 _drafts/2016-09-27-utiliser-le-composant-workflow-de-symfony.html delete mode 100644 _drafts/2016-09-29-symfony-workflow-component.html delete mode 100644 _drafts/2016-12-01-creer-votre-premier-package-pour-atom.html delete mode 100644 _drafts/2016-12-05-create-atom-package.html delete mode 100644 _drafts/2017-01-17-redux-structurez-vos-applications-front.html delete mode 100644 _drafts/2017-01-20-redux-structure-frontend-applications.html delete mode 100644 _drafts/2017-02-22-consul-service-discovery-failure-detection-2.html delete mode 100644 _drafts/2017-02-22-consul-service-discovery-failure-detection.html create mode 100644 _posts/2016-07-19-behat-structure-functional-tests.md create mode 100644 _posts/2016-07-19-behat-structurez-vos-tests-fonctionnels.md create mode 100644 _posts/2016-09-27-utiliser-le-composant-workflow-de-symfony.md create mode 100644 _posts/2016-09-29-symfony-workflow-component.md create mode 100644 _posts/2016-12-01-creer-votre-premier-package-pour-atom.md create mode 100644 _posts/2016-12-05-create-atom-package.md create mode 100644 _posts/2017-01-17-redux-structurez-vos-applications-front.md create mode 100644 _posts/2017-01-20-redux-structure-frontend-applications.md create mode 100644 _posts/2017-02-22-consul-service-discovery-failure-detection-2.md create mode 100644 _posts/2017-02-22-consul-service-discovery-failure-detection.md create mode 100644 _posts/2017-04-11-http2-nest-pas-le-futur-cest-le-present.md create mode 100644 _posts/2017-04-12-http2-future-present.md create mode 100644 _posts/2017-05-09-retours-sur-la-dotscale-2017.md create mode 100644 img/authors/vcomposieux.jpg diff --git a/_authors/vcomposieux.md b/_authors/vcomposieux.md new file mode 100644 index 000000000..b74d26855 --- /dev/null +++ b/_authors/vcomposieux.md @@ -0,0 +1,6 @@ +--- +layout: author +login: vcomposieux +name: Vincent Composieux +--- +Architecte passionné par les technologies web depuis de longues années, je pratique principalement du PHP (Symfony) / Javascript mais aussi du Python ou Golang. diff --git a/_drafts/2016-07-19-behat-structure-functional-tests.html b/_drafts/2016-07-19-behat-structure-functional-tests.html deleted file mode 100644 index 843132f2b..000000000 --- a/_drafts/2016-07-19-behat-structure-functional-tests.html +++ /dev/null @@ -1,293 +0,0 @@ ---- -layout: post -title: 'Behat: structure your functional tests' -author: vcomposieux -date: '2016-07-19 14:15:31 +0200' -date_gmt: '2016-07-19 12:15:31 +0200' -categories: -- Non classé -tags: [] ---- -{% raw %} -

Introduction

-

In order to ensure that your application is running well, it's important to write functional tests.
-Behat is the most used tool with Symfony to handle your functional tests and that's great because it's really a complete suite.
-You should nevertheless know how to use it wisely in order to cover useful and complete test cases and that's the goal of this blog post.

-

 

-

Functional testing: what's that?

-

When we are talking about functional testing we often mean that we want to automatize human-testing scenarios over the application.

-

However, it is important to write the following test types to cover the functional scope:

- -

Idea is to develop and run both integration tests and interface tests with Behat.

-

Before we can go, please note that we will use a Selenium server which will receive orders by Mink (a Behat extension) and will pilot our browser (Chrome in our configuration).

-

To be clear on the architecture we will use, here is a scheme that will resume the role of all elements:

-

[caption id="attachment_1997" align="alignnone" width="781"]Behat architecture schema Behat architecture scheme[/caption]

-

 

-

Behat set up

-

First step is to install Behat and its extensions as dependencies in our composer.json file:

-
"require-dev": {
-    "behat/behat": "~3.1",
-    "behat/symfony2-extension": "~2.1",
-    "behat/mink": "~1.7",
-    "behat/mink-extension": "~2.2",
-    "behat/mink-selenium2-driver": "~1.3",
-    "emuse/behat-html-formatter": "dev-master"
-}
-

In order to make your future contexts autoloaded, you also have to add this little PSR-4 section:

-
"autoload-dev": {
-    "psr-4": {
-      "Acme\Tests\Behat\Context\": "features/context/"
-    }
-}
-

Now, let's create our behat.yml file in our project root directory in order to define our tests execution.

-

Here is the configuration file we will start with:

-
default:
-    suites: ~
-    extensions:
-        Behat\Symfony2Extension: ~
-        Behat\MinkExtension:
-            base_url: "http://acme.tld/"
-            selenium2:
-                browser: chrome
-                wd_host: 'http://selenium-host:4444/wd/hub'
-            default_session: selenium2
-        emuse\BehatHTMLFormatter\BehatHTMLFormatterExtension:
-            name: html
-            renderer: Twig,Behat2
-            file_name: index
-            print_args: true
-            print_outp: true
-            loop_break: true
-    formatters:
-        pretty: ~
-        html:
-            output_path: %paths.base%/web/reports/behat
-

We will talk of all of these sections in their defined order so let's start with the suites section which is empty at this time but we will implement it later when we will have some contexts to add into it.

-

Then, we load some Behat extensions:

- -

Finally, in the formatters section we keep the pretty formatter in order to keep an output in our terminal and the HTML reports will be generated at the same time in the web/reports/behat directory in order to make them available over HTTP (it should not be a problem as you should not execute functional tests in production, be careful to restrict access in this case).

-

Now that Behat is ready and configured we will prepare our functional tests that we will split into two distinct Behat suites: integration and interface.

-

 

-

Writing functional tests (features)

-

In our example, we will write tests in order to ensure that a new user can register over a registration page.

-

We will have to start by writing our tests scenarios (in a .feature file) that we will put into a features/ directory located at the project root directory.

-

So for instance, we will have the following scenario:

-

File: features/registration/register.feature:

-

 

-
Feature: Register
-  In order to create an account
-  As a user
-  I want to be able to register on the application
-
-Scenario: I register when I fill my username and password only
-  Given I am on the registration page
-    And I register with username "johndoe" and password "azerty123"
-  When I submit the form
-  Then I should see the registration confirmation
-

 

-

Integration tests

-

As said previously, these tests are here to ensure all code written for the registration page can be executed and linked without any errors.

-

To do so, we will create a new integration context that concerns the registration part under directory features/context/registration:

-

File: features/context/registration/IntegrationRegisterContext:

-
<?php
-
-namespace Acme\Tests\Behat\Context\Registration;
-
-use Acme\AppBundle\Entity\User;
-use Acme\AppBundle\Registration\Registerer;
-use Behat\Behat\Context\Context;
-
-/**
- * Integration register context.
- */
-class IntegrationRegisterContext implements Context
-{
-    /**
-     * Registerer
-     */
-    protected $registerer;
-
-    /**
-     * User
-     */
-    protected $user;
-
-    /**
-     * boolean
-     */
-    protected $response;
-
-    /**
-     * Constructor.
-     *
-     * @param Registerer $registerer
-     */
-    public function __construct(Registerer $registerer)
-    {
-        $this->registerer = $registerer;
-    }
-
-    /**
-     * @Given I am on the registration page
-     */
-    public function iAmOnTheRegistrationPage()
-    {
-        $this->user = new User();
-    }
-
-    /**
-     * @Given /I register with username "(?P<username>[^"]*)" and password "(?P<password>[^"]*)"/
-     */
-    public function iRegisterWithUsernameAndPassword($username, $password)
-    {
-        $this->user->setUsername($username);
-        $this->user->setPassword($password);
-    }
-
-    /**
-     * @When I submit the form
-     */
-    public function iSubmitTheForm()
-    {
-        $this->response = $this->registerer->register($this->user);
-    }
-
-    /**
-     * @Then I should see the registration confirmation message
-     */
-    public function iShouldSeeTheRegistrationConfirmation()
-    {
-        if (!$this->response) {
-            throw new \RuntimeException('User is not registered.');
-        }
-    }
-}
-

Integration test for this part is now done for our feature. Let's write the interface test now!

-

 

-

Interface tests

-

This test will be based on the same feature file without modifying the original written scenarios we wrote at the beginning. That's why it is important to write a generic test that can be implemented both in an integration test and in an interface test.

-

So let's create that context that will be used for interface test (prefixed by Mink in our case, but you can prefix it by anything you want) under the directory features/context/registration.

-

File: features/context/registration/MinkRegisterContext:

-

 

-
<?php
-
-namespace Acme\Tests\Behat\Context\Registration;
-
-use Acme\AppBundle\Entity\User;
-use Acme\AppBundle\Registration\Registerer;
-use Behat\Behat\Context\Context;
-use Behat\MinkExtension\Context\MinkContext;
-
-/**
- * Mink register context.
- */
-class MinkRegisterContext extends MinkContext
-{
-    /**
-     * @Given I am on the registration page
-     */
-    public function iAmOnTheRegistrationPage()
-    {
-        $this->visit('/register');
-    }
-
-    /**
-     * @Given /I register with username "(?P<username>[^"]*)" and password "(?P<password>[^"]*)"/
-     */
-    public function iRegisterWithUsernameAndPassword($username, $password)
-    {
-        $this->fillField('registration[username]', $username);
-        $this->fillField('registration[password]', $password);
-    }
-
-    /**
-     * @When I submit the form
-     */
-    public function iSubmitTheForm()
-    {
-        $this->pressButton('Register');
-    }
-
-    /**
-     * @Then I should see the registration confirmation message
-     */
-    public function iShouldSeeTheRegistrationConfirmation()
-    {
-        $this->assertPageContainsText('Congratulations, you are now registered!');
-    }
-}
-

We just implemented an interface test based on the same scenario that the one we used for integration test so this class has exactly the same four methods with the same Behat annotations that we have implemented in our integration test class.

-

The only difference here is that in this context we ask Mink to ask to Selenium to do actions on the interface of our application by executing a browser instead of testing the code itself.

-

 

-

Context definition

-

One more thing now, we have to add previously created contexts in our suites section in the behat.yml configuration file.

-
    suites:
-        integration:
-            paths:
-                - %paths.base%/features/registration
-            contexts:
-                - Acme\Tests\Behat\Context\Registration\IntegrationRegisterContext:
-                    - "@acme.registration.registerer"
-        interface:
-            paths:
-                - %paths.base%/features/registration
-            contexts:
-                - Behat\MinkExtension\Context\MinkContext: []
-                - Acme\Tests\Behat\Context\Registration\MinkRegisterContext: []
-

It is important to see here that we can clearly split these kind of tests into two distinct parts integration and interface: each one will be executed with its own contexts.

-

Also, as we have loaded the Symfony2 extension during the Behat set up, we have the possibility to inject Symfony services in our contexts and that case occurs here with the acme.registration.registerer service.

-

 

-

Tests execution

-

In order to run all tests, simply execute in the project root directory: bin/behat -c behat.yml.

-

If you want to run the integration tests only: bin/behat -c behat.yml --suite=integration.

-

HTML report will be generated under the web/reports/behat/ as specified in the configuration that will allow you to have a quick overview of failed tests which is cool when you have a lot of tests.

-

 

-

Link multiple contexts together

-

At last, sometime you could need information from another context. For instance, imagine that you have a second step just after the register step. You will have to create two new IntegrationProfileContext and MinkProfileContext contexts.
-We will only talk about integration context in the following to simplify understanding.
-In this new step IntegrationProfileContext, you need some information obtained in the first step IntegrationRegisterContext.

-

This can be achieved thanks to the @BeforeScenario Behat annotation.

-

File: features/context/registration/IntegrationProfileContext:

-

 

-
<?php
-
-namespace Acme\Tests\Behat\Context\Registration;
-
-use Behat\Behat\Context\Context;
-use Behat\Behat\Hook\Scope\BeforeScenarioScope;
-
-/**
- * Integration registration profile  context.
- */
-class IntegrationProfileContext implements Context
-{
-    /**
-     * IntegrationRegisterContext
-     */
-    protected $registerContext;
-
-    /**
-     * @BeforeScenario
-     */
-    public function gatherContexts(BeforeScenarioScope $scope)
-    {
-        $environment = $scope->getEnvironment();
-
-        $this->registerContext = $environment->getContext(
-            'Acme\Tests\Behat\Context\Registration\IntegrationRegisterContext'
-        );
-    }
-}
-

You now have an accessible property $registerContext and can access informations from this context.

-

 

-

Conclusion

-

Everything starts from well-written tests which have to be thoughtful in order to allow a technical implementation on both integration tests and interface tests.
-The choosen structure about classifying its tests is also important in order to quickly find tests when the application grows.

-{% endraw %} diff --git a/_drafts/2016-07-19-behat-structurez-vos-tests-fonctionnels.html b/_drafts/2016-07-19-behat-structurez-vos-tests-fonctionnels.html deleted file mode 100644 index cc8417126..000000000 --- a/_drafts/2016-07-19-behat-structurez-vos-tests-fonctionnels.html +++ /dev/null @@ -1,291 +0,0 @@ ---- -layout: post -title: 'Behat : structurez vos tests fonctionnels' -author: vcomposieux -date: '2016-07-19 10:16:11 +0200' -date_gmt: '2016-07-19 08:16:11 +0200' -categories: -- Symfony -- Php -tags: [] ---- -{% raw %} -

Introduction

-

Il est important de mettre en place des tests fonctionnels sur les projets afin de s'assurer du bon fonctionnement de l'application.
-Lorsqu'il s'agit d'une application Symfony, Behat est l'outil le plus souvent utilisé pour réaliser ces tests et c'est tant mieux car cet outil est très complet.
-Il faut néanmoins savoir l'utiliser à bon escient afin de couvrir des cas de tests utiles et complets, c'est ce que nous allons voir dans cet article.

-

 

-

Tests fonctionnels : qu'est-ce ?

-

Lorsque nous parlons de "tests fonctionnels", nous entendons bien souvent vouloir tester l'interface de l'application (site web), autrement dit, automatiser des tests qui pourraient être faits par un humain.

-

Or, il est important d'écrire les cas de tests suivants afin de couvrir le périmètre fonctionnel :

- -

Il conviendra alors de lancer à la fois les tests d'intégration et les tests d'interface avec Behat.

-

Avant de commencer, notez que dans cet exemple, nous allons utiliser un serveur Selenium qui recevra les informations fournies par Mink (extension de Behat) et qui pilotera ensuite notre navigateur (Chrome, dans notre configuration).

-

Pour être clair sur l'architecture, voici un schéma qui résume le rôle de chacun :

-

[caption id="attachment_1986" align="alignnone" width="781"]Schéma d'architecture Behat/Selenium Schéma d'architecture Behat/Selenium[/caption]

-

 

-

Mise en place de Behat

-

La première étape est d'installer Behat et ses extensions en tant que dépendance dans notre fichier composer.json :

-
"require-dev": {
-    "behat/behat": "~3.1",
-    "behat/symfony2-extension": "~2.1",
-    "behat/mink": "~1.7",
-    "behat/mink-extension": "~2.2",
-    "behat/mink-selenium2-driver": "~1.3",
-    "emuse/behat-html-formatter": "dev-master"
-}
-

Afin que vos futurs contextes soient autoloadés, nous allons également ajouter la section PSR-4 suivante :

-
"autoload-dev": {
-    "psr-4": {
-        "Acme\Tests\Behat\Context\": "features/context/"
-    }
-}
-

Maintenant, créons le fichier de configuration behat.yml à la racine de notre projet afin d'architecturer nos tests.

-

Voici le fichier de configuration à partir duquel nous allons débuter :

-
default:
-    suites: ~
-    extensions:
-        Behat\Symfony2Extension: ~
-        Behat\MinkExtension:
-            base_url: "http://acme.tld/"
-            selenium2:
-                browser: chrome
-                wd_host: 'http://selenium-host:4444/wd/hub'
-            default_session: selenium2
-        emuse\BehatHTMLFormatter\BehatHTMLFormatterExtension:
-            name: html
-            renderer: Twig,Behat2
-            file_name: index
-            print_args: true
-            print_outp: true
-            loop_break: true
-    formatters:
-        pretty: ~
-        html:
-            output_path: %paths.base%/web/reports/behat
-

Si nous prenons les sections dans leur ordre, nous avons avant tout une section suites pour le moment vide mais que nous allons alimenter par la suite de cet article.

-

Ensuite, nous chargeons ici plusieurs extensions de Behat :

- -

Notons enfin que dans la section formatters, nous conservons le formatter pretty afin d'avoir une sortie sympa sur notre terminal et que les rapports HTML seront quant à eux générés dans le répertoire web/reports/behat afin qu'ils soient accessibles en HTTP (à priori pas de soucis car vous ne devriez pas jouer ces tests en production, attention à la restriction d'accès si c'est le cas).

-

Maintenant que Behat est prêt et configuré, nous allons préparer nos tests fonctionnels que nous allons découper en deux "suites" Behat distinctes : integration et interface.

-

 

-

Ecriture des tests fonctionnels (features)

-

Nous allons partir sur des tests permettant de s'assurer du bon fonctionnement d'une page d'inscription.

-

Nous devons avant tout écrire nos scénarios de tests fonctionnels (fichier .feature) que nous allons placer dans un répertoire features/ à la racine du projet.

-

Nous allons donc avoir, par exemple, le scénario suivant :

-

Fichier features/registration/register.feature :

-
Feature: Register
-    In order to create an account
-    As a user
-    I want to be able to register on the application
-
-Scenario: I register when I fill my username and password only
-    Given I am on the registration page
-        And I register with username "johndoe" and password "azerty123"
-    When I submit the form
-    Then I should see the registration confirmation
-

 

-

Tests d'intégration

-

Il va maintenant convenir d'implémenter le code qui va nous permettre de tester que le code écrit pour l'inscription d'un utilisateur peut être exécuté et enchaîné sans erreur.

-

Nous allons donc créer un contexte d'intégration propre à l'inscription sous le répertoire features/context/registration :

-

Fichier : features/context/registration/IntegrationRegisterContext :

-
<?php
-
-namespace Acme\Tests\Behat\Context\Registration;
-
-use Acme\AppBundle\Entity\User;
-use Acme\AppBundle\Registration\Registerer;
-use Behat\Behat\Context\Context;
-
-/**
- * Integration register context.
- */
-class IntegrationRegisterContext implements Context
-{
-    /**
-     * Registerer
-     */
-    protected $registerer;
-
-    /**
-     * User
-     */
-    protected $user;
-
-    /**
-     * boolean
-     */
-    protected $response;
-
-    /**
-     * Constructor.
-     *
-     * @param Registerer $registerer
-     */
-    public function __construct(Registerer $registerer)
-    {
-        $this->registerer = $registerer;
-    }
-
-    /**
-     * @Given I am on the registration page
-     */
-    public function iAmOnTheRegistrationPage()
-    {
-        $this->user = new User();
-    }
-
-    /**
-     * @Given /I register with username "(?P<username>[^"]*)" and password "(?P<password>[^"]*)"/
-     */
-    public function iRegisterWithUsernameAndPassword($username, $password)
-    {
-        $this->user->setUsername($username);
-        $this->user->setPassword($password);
-    }
-
-    /**
-     * @When I submit the form
-     */
-    public function iSubmitTheForm()
-    {
-        $this->response = $this->registerer->register($this->user);
-    }
-
-    /**
-     * @Then I should see the registration confirmation message
-     */
-    public function iShouldSeeTheRegistrationConfirmation()
-    {
-        if (!$this->response) {
-            throw new \RuntimeException('User is not registered.');
-        }
-    }
-}
-

L'implémentation du test d'intégration est terminé pour cette feature !
-Passons maintenant au test d'interface !

-

 

-

Tests d'interface

-

Ce test va se baser sur la même feature et nous n'avons absolument rien modifié dans le test précédemment écrit. C'est pourquoi il est important de bien rédiger ses tests fonctionnels afin qu'ils restent assez génériques pour être implémentés à la fois en test d'intégration et en test d'interface.

-

Créons donc le contexte qui sera utilisé pour le test d'interface (préfixé par Mink dans notre cas, mais vous pouvez préfixer par ce que vous voulez) sous le même répertoire features/context/registration :

-

Fichierfeatures/context/registration/MinkRegisterContext :

-
<?php
-
-namespace Acme\Tests\Behat\Context\Registration;
-
-use Acme\AppBundle\Entity\User;
-use Acme\AppBundle\Registration\Registerer;
-use Behat\Behat\Context\Context;
-use Behat\MinkExtension\Context\MinkContext;
-
-/**
- * Mink register context.
- */
-class MinkRegisterContext extends MinkContext
-{
-    /**
-     * @Given I am on the registration page
-     */
-    public function iAmOnTheRegistrationPage()
-    {
-        $this->visit('/register');
-    }
-
-    /**
-     * @Given /I register with username "(?P<username>[^"]*)" and password "(?P<password>[^"]*)"/
-     */
-    public function iRegisterWithUsernameAndPassword($username, $password)
-    {
-        $this->fillField('registration[username]', $username);
-        $this->fillField('registration[password]', $password);
-    }
-
-    /**
-     * @When I submit the form
-     */
-    public function iSubmitTheForm()
-    {
-        $this->pressButton('Register');
-    }
-
-    /**
-     * @Then I should see the registration confirmation message
-     */
-    public function iShouldSeeTheRegistrationConfirmation()
-    {
-        $this->assertPageContainsText('Congratulations, you are now registered!');
-    }
-}
-

Nous venons d'implémenter un test d'interface basé sur le même scénario que celui que nous avons utilisé pour notre test d'intégration, reprenant exactement les quatre méthodes implémentées précédemment avec les mêmes annotations Behat.

-

La seule différence est que dans ce contexte, Mink va demander à Selenium d'effectuer les actions au niveau de l'interface de notre application en pilotant un navigateur au lieu de tester le code lui-même.

-

 

-

Définitions des contextes

-

Il ne nous reste plus qu'à ajouter les contextes créés précédemment sous notre section suites dans le fichier de configuration behat.yml :

-
    suites:
-        integration:
-            paths:
-                - %paths.base%/features/registration
-            contexts:
-                - Acme\Tests\Behat\Context\Registration\IntegrationRegisterContext:
-                    - "@acme.registration.registerer"
-        interface:
-            paths:
-                - %paths.base%/features/registration
-            contexts:
-                - Behat\MinkExtension\Context\MinkContext: []
-                - Acme\Tests\Behat\Context\Registration\MinkRegisterContext: []
-

Il est important de voir ici que nous découpons clairement les tests en deux suites distinctes : integration et interface : chacune d'entre elles sera exécutée avec les contextes qui lui sont propres.

-

Etant donné que nous avons chargés l'extension Symfony2 lors de la mise en place de Behat, nous avons la possibilité d'injecter des services Symfony dans nos contextes, c'est le cas ici avec le service acme.registration.registerer.

-

 

-

Exécution des tests

-

Pour lancer tous les tests, exécutez simplement, à la racine du projet : bin/behat -c behat.yml.

-

Pour lancer uniquement la suite d'integration, par exemple : bin/behat -c behat.yml --suite=integration.

-

Le rapport HTML est quand à lui généré dans web/reports/behat/, comme spécifié dans notre configuration, ce qui vous permettra d'avoir un aperçu rapide des tests qui échouent, plutôt pratique lorsque vous avez de nombreux tests.

-

 

-

Lier plusieurs contextes entre eux

-

Pour terminer, vous pourrez parfois avoir besoin de lier les contextes entre eux. Par exemple, imaginons que vous ayez une deuxième page sur votre formulaire d'inscription pour renseigner les informations personnelles, vous allez alors créer deux nouveaux contextes IntegrationProfileContext et MinkProfileContext.
-Partons sur le contexte d'intégration pour simplifier l'explication, l'idée est de ne pas dupliquer le code précédemment créé et permettant de tester la première étape IntegrationRegisterContext et de réutiliser ces informations dans le nouveau contexte IntegrationProfileContext.

-

Ceci est possible grâce à l'annotation @BeforeScenario de Behat.

-

Fichier : features/context/registration/IntegrationProfileContext :

-
<?php
-
-namespace Acme\Tests\Behat\Context\Registration;
-
-use Behat\Behat\Context\Context;
-use Behat\Behat\Hook\Scope\BeforeScenarioScope;
-
-/**
- * Integration registration profile  context.
- */
-class IntegrationProfileContext implements Context
-{
-    /**
-     * IntegrationRegisterContext
-     */
-    protected $registerContext;
-
-    /**
-     * @BeforeScenario
-     */
-    public function gatherContexts(BeforeScenarioScope $scope)
-    {
-        $environment = $scope->getEnvironment();
-
-        $this->registerContext = $environment->getContext(
-            'Acme\Tests\Behat\Context\Registration\IntegrationRegisterContext'
-        );
-    }
-}
-

Vous avez maintenant à disposition une propriété $registerContext et pouvez accéder à des informations qui proviennent du contexte précédent.

-

 

-

Conclusion

-

Tout part de l'écriture des tests fonctionnels qui doivent être bien réfléchis pour ensuite permettre une implémentation technique à la fois en test d'intégration mais aussi en test d'interface.
-La structure choisie pour classer ses tests fonctionnels est aussi importante pour pouvoir s'y retrouver rapidement dans les différents scénarios de test lorsque l'application prend de l'ampleur.

-{% endraw %} diff --git a/_drafts/2016-09-27-utiliser-le-composant-workflow-de-symfony.html b/_drafts/2016-09-27-utiliser-le-composant-workflow-de-symfony.html deleted file mode 100644 index c5c4a691b..000000000 --- a/_drafts/2016-09-27-utiliser-le-composant-workflow-de-symfony.html +++ /dev/null @@ -1,159 +0,0 @@ ---- -layout: post -title: Utiliser le composant Workflow de Symfony -author: vcomposieux -date: '2016-09-27 11:05:28 +0200' -date_gmt: '2016-09-27 09:05:28 +0200' -categories: -- Non classé -- Symfony -- Php -tags: -- symfony -- workflow ---- -{% raw %} -

Depuis Symfony 3.2, un nouveau composant très utile a vu le jour : le composant Workflow.
-Celui-ci est en effet très pratique et peut très largement simplifier vos développements lorsque vous avez, par exemple, à gérer des workflows de statut dans votre application.

-

 

-

Installation

-

Dans tous les cas, vous devez installer la dépendance suivante :

-
"symfony/workflow": "~3.2@dev"
-

Si vous utilisez une version antérieure de Symfony mais >=2.3, c'est aussi possible mais il vous faudra également installer ce bundle non-officiel qui embarque le composant et ajoute la configuration nécessaire sous le namespace du bundle :

-
"fduch/workflow-bundle": "~0.2@dev"
-

Pensez bien à activer le bundle dans votre kernel.

-

 

-

Configuration

-

Il va maintenant nous falloir définir la configuration de notre workflow et ainsi définir les statuts (appelés places) et transitions possibles.

-

Pour cet article, nous sommes partis sur un exemple basé sur les statuts d'une pull request. Celle-ci peut avoir les états suivants : opened , closed , needs_review , reviewed  et enfin merged .

-

Cependant, elle ne pourra, par exemple, pas être passée en merged  sans être passée par le statut reviewed . C'est ici que le composant Workflow prend tout son sens.

-

 

-

Voici ce que donne notre configuration complète :

-
workflow:
-    workflows:
-        pull_request:
-            marking_store:
-                type: multiple_state
-                arguments:
-                    - state
-            supports:
-                - AppBundle\Entity\PullRequest
-            places:
-                - opened
-                - closed
-                - needs_review
-                - reviewed
-                - merged
-            transitions:
-                feedback:
-                    from: opened
-                    to:   needs_review
-                review:
-                    from: [opened, needs_review]
-                    to:   reviewed
-                merge:
-                    from: reviewed
-                    to:   merged
-                close:
-                    from: [opened, needs_review, reviewed]
-                    to:   closed
-

Nous spécifions ici que nous souhaitons utiliser un workflow de type multiple_state . Notez que si vous souhaitez utiliser une transition simple d'un statut vers un autre, vous pouvez utiliser ici single_state.

-

Nous disposons donc également d'une classe AppBundle\Entity\PullRequest  qui dispose d'une propriété state  ainsi que son setter et getter associé (le composant va utiliser les méthodes getter et setter pour changer l'état et/ou obtenir l'état courant) :

-
<?php
-
-namespace AppBundle\Entity;
-
-use Doctrine\ORM\Mapping as ORM;
-
-/**
- * @ORM\Table(name="pull_request")
- */
-class PullRequest
-{
-    /**
-     * @ORM\Column(type="json_array", nullable=true)
-     */
-    protected $state;
-
-    public function setState($state)
-    {
-        $this->state = $state;
-    }
-
-    public function getState()
-    {
-        return $this->state;
-    }
-}
-

 

-

Nous avons terminé, nous pouvons maintenant commencer à utiliser le composant Workflow !

-

 

-

Utilisation

-

La première chose utile à effectuer après avoir écrit votre workflow est de générer une représentation graphique de celui-ci (sous un format Graphviz).

-

Pour se faire, nous utilisons la commande Symfony :

-
$ bin/console workflow:dump pull_request
-

 

-

Celle-ci va vous générer un code Graphviz qui donne le schéma suivant :

-

Workflow - Graphviz

-

Celui-ci permet vraiment de donner une vision claire de son workflow, à tous les niveaux (développeurs, product owners, clients, ...).

-

Le composant Workflow implémente des méthodes permettant d'effectuer une transition, vérifier si une transition peut être effectuée avec l'état actuel et lister les transitions possibles avec l'état actuel.

-

 

-

Pour vérifier si vous pouvez effectuer une transition et l'appliquer, rien de plus simple :

-
<?php
-
-namespace AppBundle\Controller;
-
-use AppBundle\Manager\PullRequestManager;
-use Symfony\Bundle\FrameworkBundle\Controller\Controller;
-use Symfony\Component\HttpFoundation\RedirectResponse;
-
-class PullRequestController extends Controller
-{
-    /**
-     * @param int $identifier A pull request identifier.
-     *
-     * @return RedirectResponse
-     */
-    public function update($identifier)
-    {
-        ...
-
-        // Notre pull request est au statut "reviewed"
-        $pullRequest = $this->getPullRequestManager()->find($identifier);
-
-        // Nous obtenons le service "workflow.<nom du workflow>"
-        $workflow = $this->get('workflow.pull_request');
-
-        if ($workflow->can($pullRequest, 'merge')) {
-            $workflow->apply($pullRequest, 'merge');
-        }
-
-        ...
-    }
-}
-

Si vous ne passez pas par la méthode can() , la méthode apply()  renverra une exception si la transition ne peut pas être effectuée. Vous pouvez donc également catcher cette exception de type Symfony\Component\Workflow\Exception\LogicException .

-

 

-

Pour lister les transitions disponibles :

-
$workflow->getEnabledTransitions($pullRequest);
-

Globalement, l'utilisation du composant se limite à ces 3 méthodes. Comme vous le remarquez, il devient très simple d'utiliser un workflow, même complexe !

-

 

-

Branchez-vous sur les événements !

-

Le composant utilise également plusieurs événements, à savoir, dans l'ordre chronologique :

- -

Enfin, sachez que ces événements existent aussi en version unique pour chaque workflow afin de vous permettre de vous brancher dessus uniquement sur certains workflows. Il vous faut alors utiliser le nom workflow.pull_request.enter.

-

Faisons encore mieux, vous pouvez même vous brancher sur une transition particulière :

- -

 

-

Conclusion

-

Le composant Workflow est vraiment très utile dans la gestion d'états ou de statuts sur la plupart des projets.

-

N'hésitez pas à l'utiliser, sa facilité de configuration et d'utilisation vous aidera grandement sur vos projets.

-

Aussi, il m'a permis de donner un graphique clair sur un workflow complexe à toutes les personnes avec qui je travaillais.

-{% endraw %} diff --git a/_drafts/2016-09-29-symfony-workflow-component.html b/_drafts/2016-09-29-symfony-workflow-component.html deleted file mode 100644 index bbb432a89..000000000 --- a/_drafts/2016-09-29-symfony-workflow-component.html +++ /dev/null @@ -1,160 +0,0 @@ ---- -layout: post -title: Use the Symfony Workflow component -author: vcomposieux -date: '2016-09-29 10:04:20 +0200' -date_gmt: '2016-09-29 08:04:20 +0200' -categories: -- Non classé -tags: [] ---- -{% raw %} -

Since Symfony 3.2, a new useful component was born: the Workflow component.

-

It is indeed really convenient and can simplify greatly your developments when you have to manage status workflows in your application, that occurs a lot.

-

 

-

Installation

-

In all cases, you have to install the following dependency:

-
"symfony/workflow": "~3.2@dev"
-

If you use an earlier version of Symfony but >=2.3 you are also able to use this component, but you have to install the following non-official bundle, which loads the component itself and add the required configuration under the bundle's namespace:

-
"fduch/workflow-bundle": "~0.2@dev"
-

Do not forget to enable the bundle in your kernel class.

-

 

-

Configuration

-

Time has come to write our workflow configuration. We will have to define all our places (statuses / states) and available transitions.

-

In this blog post, we will take a pull request status example. A pull request can have one of the following status: opened , closed , needs_review , reviewed  or merged.

-

However, it cannot be, for instance, moved from the merged status without having the reviewed  status before. The workflow component makes sense here.

-

 

-

Here is our full workflow configuration:

-
workflow:
-    workflows:
-        pull_request:
-            marking_store:
-                type: multiple_state
-                arguments:
-                    - state
-            supports:
-                - AppBundle\Entity\PullRequest
-            places:
-                - opened
-                - closed
-                - needs_review
-                - reviewed
-                - merged
-            transitions:
-                feedback:
-                    from: opened
-                    to:   needs_review
-                review:
-                    from: [opened, needs_review]
-                    to:   reviewed
-                merge:
-                    from: reviewed
-                    to:   merged
-                close:
-                    from: [opened, needs_review, reviewed]
-                    to:   closed
-

Here, we specify we want to use a multiple_state  workflow. Please not that if you want to use a simple transition from one state to another, you can use a single_state .

-

For this example, we also have defined a AppBundle\Entity\PullRequest class which has a state  property and associated setter and getter methods (component will use these methods to manage transitions):

-
<?php
-
-namespace AppBundle\Entity;
-
-use Doctrine\ORM\Mapping as ORM;
-
-/**
- * @ORM\Table(name="pull_request")
- */
-class PullRequest
-{
-    /**
-     * @ORM\Column(type="json_array", nullable=true)
-     */
-    protected $state;
-
-    public function setState($state)
-    {
-        $this->state = $state;
-    }
-
-    public function getState()
-    {
-        return $this->state;
-    }
-}
-

 

-

 

-

Everything is now ready, we can start to use the Workflow component!

-

 

-

Usage

-

First useful thing to do after you have written your workflow configuration is to generate a graph using the Symfony command. The command will generate one graph using the Graphviz format.

-

 

-

Here is the Symfony command you have to run:

-
$ bin/console workflow:dump pull_request
-

The generated Graphviz will give you the following diagram:

-

Workflow - Graphviz

-

This one gives you a really clear vision of your workflow and allows everyone at every level (developers, product owners, customers, ...) to understand the business logic.

-

The Workflow component implements methods that allow you to verify if a transition is applicable and to later apply it depending on the current status and to also list all enabled transitions.

-

 

-

In order to check if you can apply a specific transition and apply it, simply use the following code:

-
<?php
-
-namespace AppBundle\Controller;
-
-use AppBundle\Manager\PullRequestManager;
-use Symfony\Bundle\FrameworkBundle\Controller\Controller;
-use Symfony\Component\HttpFoundation\RedirectResponse;
-
-class PullRequestController extends Controller
-{
-    /**
-     * @param int $identifier A pull request identifier.
-     *
-     * @return RedirectResponse
-     */
-    public function update($identifier)
-    {
-        ...
-
-        // Notre pull request est au statut "reviewed"
-        $pullRequest = $this->getPullRequestManager()->find($identifier);
-
-        // Nous obtenons le service "workflow.<nom du workflow>"
-        $workflow = $this->get('workflow.pull_request');
-
-        if ($workflow->can($pullRequest, 'merge')) {
-            $workflow->apply($pullRequest, 'merge');
-        }
-
-        ...
-    }
-}
-

In the case you do not want to use the can() method, the apply()  one will throw an exception if the transition cannot be effectively done, so you will be able to catch exceptions on the Symfony\Component\Workflow\Exception\LogicException type.

-

 

-

 

-

To list all enabled transitions:

-
$workflow->getEnabledTransitions($pullRequest);
-

Overall, the component usage is just as simple as these 3 methods. As you can see, complex workflows are now easier to manage!

-

 

-

Tune in for events!

-

The component also dispatches multiple events, chronologically sorted as:

- -

 

-

Finally, you have to know that these events also exist in a unique way for each workflow in order to allow you to tune in your workflow events only.
-If you want to do that, you have to listen to the following name: workflow.pull_request.enter .

-

 

-

Let's do better than that: you are also able to listen to a specific transition or a state for a specific workflow:

- -

 

-

Conclusion

-

The Workflow component is a really useful component to manage state or status on most of web applications.

-

Do not hesitate to use it because its simplicity of configuration and use will probably help you a lot on your projects.

-

Also, this component helps me a lot to give people I was working with a clear vision on a complex workflow we have to manage. The graph generation allows to clarify all of that for everyone!

-{% endraw %} diff --git a/_drafts/2016-12-01-creer-votre-premier-package-pour-atom.html b/_drafts/2016-12-01-creer-votre-premier-package-pour-atom.html deleted file mode 100644 index 2b2e33959..000000000 --- a/_drafts/2016-12-01-creer-votre-premier-package-pour-atom.html +++ /dev/null @@ -1,286 +0,0 @@ ---- -layout: post -title: Créer votre premier package pour Atom -author: vcomposieux -date: '2016-12-01 12:14:17 +0100' -date_gmt: '2016-12-01 11:14:17 +0100' -categories: -- Javascript -tags: -- atom -- package -- babel -- jasmine ---- -{% raw %} -

Introduction à Atom

-

Atom est un éditeur de texte (principalement utilisé pour du code) multi-plateforme développé par la société GitHub et qui s'appuie sur un autre framework développé par GitHub : Electron, qui permet de développer des applications natives pour chaque système d'exploitation à partir de code Javascript.

-

Le grand intérêt d'Atom est qu'il peut être étendu très facilement avec un peu de code Javascript et c'est ce que nous allons voir dans cet article. Ainsi, tout le monde peut écrire son "package" pour Atom.

-

Aussi, sa communauté très active compte déjà un bon nombre de packages : 5 285 au moment où j'écris cet article.
-Vous pouvez les retrouver à l'URL suivante : https://atom.io/packages

-

Si toutefois vous ne trouvez pas votre bonheur dans les packages déjà proposés, vous pouvez alors écrire le votre et nous allons voir qu'il n'y a rien de compliqué.

-

 

-

Générer son premier package

-

Pour créer votre premier package, rassurez-vous, vous n'allez pas partir de rien. En effet, nous allons utiliser la commande fournie par le package Package Generator  natif à Atom.

-

Pour se faire, il vous suffira de naviguer dans : Packages -> Package Generator -> Generate Atom Package.

-

[note]Lors de la génération, vous pouvez choisir le langage que vous souhaitez utiliser pour développer votre package, entre Javascript et Coffeescript. Cet article est rédigé en Javascript.[/note]

-

Atom vous ouvrira alors une nouvelle fenêtre à l'intérieur de votre nouveau package, nommé my-package .

-

 

-

Structure d'un package

-

Nous allons maintenant détailler la structure par défaut du projet :

-
├── CHANGELOG.md
-├── LICENSE.md
-├── README.md
-├── keymaps
-│   └── my-package.json         <- Raccourcis clavier enregistrés par votre package
-├── lib
-│   ├── my-package-view.js
-│   └── my-package.js           <- Point d'entrée de votre package
-├── menus
-│   └── my-package.json         <- Déclaration des menus que votre package ajoute dans Atom
-├── package.json                <- Fichier descriptif et de dépendances de votre package
-├── spec                        <- Répertoire de tests (Jasmine) de votre package
-│   ├── my-package-spec.js
-│   └── my-package-view-spec.js
-└── styles                      <- Feuilles de styles utilisées par votre package
-└── my-package.less
-

 

-

Le premier élément à renseigner est le fichier package.json  qui doit contenir les informations relatives à votre package tel que son nom, sa version, license, mots clés pour trouver votre package et également ses librairies de dépendances.

-

Notez également la présence dans ce fichier d'une section activationCommands  qui vous permet de définir la commande exécutée lors de l'activation de votre package.

-

Nous avons ensuite le fichier keymaps/my-package.json  qui vous permet d'enregistrer des raccourcis clavier dans votre application, de façon très simple :

-
{
-  "atom-workspace": {
-    "ctrl-alt-p": "my-package:toggle"
-  }
-}
-

 

-

Passons maintenant au point d'entrée de votre package. Il s'agit de ce qui se trouve dans lib/my-package.js .

-

Dans ce fichier est exporté un objet par défaut qui contient une propriété subscriptions  et des méthodes activate()  et deactivate()  notamment.

-

Lors de l'activation de notre package (dans la méthode activate() ), nous allons enregistrer dans notre propriété subscriptions  un objet de type CompositeDisposable  qui nous permettra d'ajouter et d'éventuellement plus tard, supprimer des commandes disponibles dans notre package :

-
activate(state) {
-  this.subscriptions = new CompositeDisposable();
-  this.subscriptions.add(atom.commands.add('atom-workspace', {
-    'my-package:toggle': () => this.toggle()
-  }));
-}
-

 

-

Notre commande étant enregistrée, nous pouvons dès maintenant l'exécuter en ouvrant la palette de commande : My Package: Toggle .
-Celle-ci va exécuter le code contenu dans la méthode toggle()  de votre classe, soit dans le package par défaut, afficher une petite fenêtre en haut de l'écran.

-

Vous pouvez ajouter autant de commandes que vous le souhaitez et surtout, découper votre code comme vous le sentez.

-

 

-

Ajouter des paramètres dans mon package

-

Vous avez la possibilité d'ajouter des paramètres à votre package et ceci est rendu possible grâce au composant Config.

-

Il vous suffira d'ajouter une propriété config  à votre classe en définissant un objet avec la définition de chaque élément que vous souhaitez voir apparaître dans vos paramètres :

-
config: {
-  "gitlabUrl": {
-    "description": "If you rely on a private Gitlab server, please type your base URI here (default: https://gitlab.com).",
-    "type": "string",
-    "default": "https://gitlab.com"
-  }
-}
-

 

-

La configuration offre un grand nombre de valeurs disponibles (boolean , color , integer , string , ...) ce qui permet de laisser un grand nombre de choix à vos utilisateurs.

-

Les paramètres de votre package apparaîtront alors pour votre package, sous Atom :

-

Atom - Settings
-

-

Vous pourrez alors, à tout moment dans votre code, obtenir dans votre package la valeur définie par l'utilisateur (ou la valeur par défaut fournie si aucune valeur n'a été renseignée) via :

-
let gitlabUrl = atom.config.get('gitlabUrl');
-

 

-

Tour d'horizon des composants

-

Vous pouvez maintenant commencer à développer votre package, nous allons donc parcourir les différents composants qui sont à votre disposition et que vous pourrez utiliser dans votre package !

-

 

-

TextEditor : Agissez sur l'éditeur de texte

-

Avec le composant TextEditor , vous allez pouvoir insérer du texte dans votre éditeur, enregistrer le fichier, jouer sur l'historique des actions (aller en avant ou arrière), déplacer le curseur dans l'éditeur, copier/coller dans le presse-papier, jouer sur l'indentation, scroller, etc ...

-

Quelques commandes en exemple, ici pour insérer du texte à une coordonnée donnée et enregistrer le fichier :

-
editor.setCursorBufferPosition([row, column]);
-editor.insertText('foo');
-editor.save();
-

 

-

ViewRegistry et View : Créez et affichez votre propre fenêtre

-

Ces composants vont vous permettre de créer votre fenêtre à l'intérieur d'Atom et de l'afficher.
-Vous disposez d'un exemple d'utilisation du composant View  dans le package généré par défaut :

-
export default class MyPackageView {
-  constructor(serializedState) {
-    // Create root element
-    this.element = document.createElement('div');
-    this.element.classList.add('my-package');
-
-    // Create message element
-    const message = document.createElement('div');
-    message.textContent = 'The MyPackage package is Alive! It
-

 

-

NotificationManager et Notification : Informez vos utilisateurs via des notifications

-

Vous avez également la possibilité de rendre des notifications dans l'éditeur de plusieurs niveaux, avec les commandes suivantes :

-
atom.notifications.addSuccess('My success notification');
-atom.notifications.addInfo('My info notification');
-atom.notifications.addWarning('My warning notification');
-atom.notifications.addError('My error notification');
-atom.notifications.addFatalError('My fatal error notification');
-

 

-

GitRepository

-

Celui-ci est très intéressant : vous pouvez en effet accéder à toutes les propriétés du repository Git actuellement utilisé par l'utilisateur.

-

Vous pourrez alors obtenir (entre autres) la branche actuellement utilisée, obtenir l'URL du remote, voir si un fichier est nouveau ou modifié ou encore accéder au diff.

-
let repository = atom.project.getRepositoryForDirectory('/path/to/project');
-
-console.log(repository.getOriginURL());               // -> git@github.com:eko/atom-pull-request.git
-console.log(repository.getShortHead());               // -> master
-console.log(repository.isStatusNew('/path/to/file')); // -> true
-

 

-

Encore bien d'autres choses à découvrir ...

-

Je vous ai présenté les composants les plus courants mais je vous invite à visiter la documentation de l'API si vous souhaitez aller plus loin : https://atom.io/docs/api/latest/AtomEnvironment

-

 

-

Tester votre package

-

Nous en arrivons au moment de tester notre package, et pour cela, Atom utilise Jasmine.

-

Votre package vient déjà avec un fichier de test pré-défini :

-
import MyPackageView from '../lib/my-package-view';
-
-describe('MyPackageView', () => {
-  it('has one valid test', () => {
-    expect('life').toBe('easy');
-  });
-});
-

 

-

 

-

Les tests Jasmine doivent être structurés de la façon suivante :

- -

C'est maintenant à vous de jouer et de tester votre logique applicative.

-

 

-

Vous pouvez lancer les specs via le menu d'Atom : View  -> Packages  -> Run Package Specs .

-

 

-

Publier votre package

-

Notre package est maintenant prêt à être publié !

-

Atom - Fusée

-

Pour se faire, nous allons utiliser l'outil CLI installé avec Atom : apm .
-Après avoir pushé votre code sur un repository Github, rendez-vous dans le répertoire de votre package et jouez la commande suivante :

-
$ apm publish --tag v0.0.1 minor
-
-Preparing and tagging a new version ✓
-Pushing v0.0.1 tag ✓
-...
-

 

-

La commande va s'occuper de créer et pusher le tag de la version spécifiée et référencer cette version sur le registry d'Atom.

-

Félicitations, votre package est maintenant publié et visible à l'URL : https://atom.io/packages/<votre-package> !

-

 

-

Intégration continue

-

Afin de vous assurer que votre package fonctionne toujours sur la version stable d'Atom mais également pour anticiper et tester également la version bêta, vous pouvez mettre en place Travis-CI sur le repository de votre code avec le fichier suivant :

-
language: objective-c
-
-notifications:
-  email:
-    on_success: never
-    on_failure: change
-
-script: 'curl -s https://raw.githubusercontent.com/nikhilkalige/docblockr/develop/spec/atom-build-package.sh | sh'
-
-env:
-  global:
-    - APM_TEST_PACKAGES=""
-
-  matrix:
-    - ATOM_CHANNEL=stable
-    - ATOM_CHANNEL=beta
-

 

-

Conclusion

-

Je trouve personnellement qu'il s'agit d'une vraie révolution de pouvoir interagir à tel point avec l'éditeur de texte, l'outil utilisé la plupart du temps par les développeurs.

-

L'API d'Atom est déjà très riche et est très simple à utiliser, c'est très certainement la raison pour laquelle la communauté offre déjà un bon nombre de packages.

-

Comme pour toute librairie, inutile de réinventer la roue et de créer des doublons dans les packages, l'idée est vraiment d'ajouter des fonctionnalités à Atom afin d'enrichir notre expérience utilisateur d'Atom.

-s ALIVE!'; - message.classList.add('message'); - this.element.appendChild(message); - } - - // ... -} - -let myPackageView = new MyPackageView(state.myPackageViewState); -let modalPanel = atom.workspace.addModalPanel({ - item: myPackageView.getElement(), - visible: false; -}); - -modalPanel.show();
-

 

-

NotificationManager et Notification : Informez vos utilisateurs via des notifications

-

Vous avez également la possibilité de rendre des notifications dans l'éditeur de plusieurs niveaux, avec les commandes suivantes :

-
atom.notifications.addSuccess('My success notification');
-atom.notifications.addInfo('My info notification');
-atom.notifications.addWarning('My warning notification');
-atom.notifications.addError('My error notification');
-atom.notifications.addFatalError('My fatal error notification');
-

 

-

GitRepository

-

Celui-ci est très intéressant : vous pouvez en effet accéder à toutes les propriétés du repository Git actuellement utilisé par l'utilisateur.

-

Vous pourrez alors obtenir (entre autres) la branche actuellement utilisée, obtenir l'URL du remote, voir si un fichier est nouveau ou modifié ou encore accéder au diff.

-
let repository = atom.project.getRepositoryForDirectory('/path/to/project');
-
-console.log(repository.getOriginURL());               // -> git@github.com:eko/atom-pull-request.git
-console.log(repository.getShortHead());               // -> master
-console.log(repository.isStatusNew('/path/to/file')); // -> true
-

 

-

Encore bien d'autres choses à découvrir ...

-

Je vous ai présenté les composants les plus courants mais je vous invite à visiter la documentation de l'API si vous souhaitez aller plus loin : https://atom.io/docs/api/latest/AtomEnvironment

-

 

-

Tester votre package

-

Nous en arrivons au moment de tester notre package, et pour cela, Atom utilise Jasmine.

-

Votre package vient déjà avec un fichier de test pré-défini :

-
import MyPackageView from '../lib/my-package-view';
-
-describe('MyPackageView', () => {
-  it('has one valid test', () => {
-    expect('life').toBe('easy');
-  });
-});
-

 

-

 

-

Les tests Jasmine doivent être structurés de la façon suivante :

- -

C'est maintenant à vous de jouer et de tester votre logique applicative.

-

 

-

Vous pouvez lancer les specs via le menu d'Atom : View  -> Packages  -> Run Package Specs .

-

 

-

Publier votre package

-

Notre package est maintenant prêt à être publié !

-

Atom - Fusée

-

Pour se faire, nous allons utiliser l'outil CLI installé avec Atom : apm .
-Après avoir pushé votre code sur un repository Github, rendez-vous dans le répertoire de votre package et jouez la commande suivante :

-
$ apm publish --tag v0.0.1 minor
-
-Preparing and tagging a new version ✓
-Pushing v0.0.1 tag ✓
-...
-

 

-

La commande va s'occuper de créer et pusher le tag de la version spécifiée et référencer cette version sur le registry d'Atom.

-

Félicitations, votre package est maintenant publié et visible à l'URL : https://atom.io/packages/<votre-package> !

-

 

-

Intégration continue

-

Afin de vous assurer que votre package fonctionne toujours sur la version stable d'Atom mais également pour anticiper et tester également la version bêta, vous pouvez mettre en place Travis-CI sur le repository de votre code avec le fichier suivant :

-
language: objective-c
-
-notifications:
-  email:
-    on_success: never
-    on_failure: change
-
-script: 'curl -s https://raw.githubusercontent.com/nikhilkalige/docblockr/develop/spec/atom-build-package.sh | sh'
-
-env:
-  global:
-    - APM_TEST_PACKAGES=""
-
-  matrix:
-    - ATOM_CHANNEL=stable
-    - ATOM_CHANNEL=beta
-

 

-

Conclusion

-

Je trouve personnellement qu'il s'agit d'une vraie révolution de pouvoir interagir à tel point avec l'éditeur de texte, l'outil utilisé la plupart du temps par les développeurs.

-

L'API d'Atom est déjà très riche et est très simple à utiliser, c'est très certainement la raison pour laquelle la communauté offre déjà un bon nombre de packages.

-

Comme pour toute librairie, inutile de réinventer la roue et de créer des doublons dans les packages, l'idée est vraiment d'ajouter des fonctionnalités à Atom afin d'enrichir notre expérience utilisateur d'Atom.

-{% endraw %} diff --git a/_drafts/2016-12-05-create-atom-package.html b/_drafts/2016-12-05-create-atom-package.html deleted file mode 100644 index 42959ae08..000000000 --- a/_drafts/2016-12-05-create-atom-package.html +++ /dev/null @@ -1,267 +0,0 @@ ---- -layout: post -title: Create your first Atom package -author: vcomposieux -date: '2016-12-05 17:34:21 +0100' -date_gmt: '2016-12-05 16:34:21 +0100' -categories: -- Non classé -tags: -- atom -- babel -- jasmine -- package ---- -{% raw %} -

Introduction to Atom

-

Atom is an open-source text editor (mostly used by developers) which is multi-platform and developed by GitHub company. It is based on Electron, the Github-developed framework, which allows developers to build native desktop applications for any operating systems by writing Javascript code.

-

The main interesting feature of Atom is that it also has a great package management tool and packages are also written in Javascript so it's quite easy for anyone to create one. This article aims to talk about it.

-

Finally, its community is also active as it already has a lot of available packages: 5 285 at this time.
-You can browse all packages by going to the following address: https://atom.io/packages

-

However, if you cannot find a package that fits your needs you can start creating your own and we will see how simple it is.

-

 

-

Generate your first package

-

In order to create your own package, don't worry, you will not start from scratch. Indeed, we will use the Package Generator  command which is brought to us by Atom core.

-

To do that, you will just have to navigate into  Packages -> Package Generator -> Generate Atom Package.

-

[note]In order to generate your package, you can choose the language between Javascript  and Coffeescript . This article will use Javascript.[/note]

-

When the command is executed, Atom will open a new window into your package project, by default named my-package .

-

 

-

Package structure

-

We will now see in details what's inside our package project directory:

-
├── CHANGELOG.md
-├── LICENSE.md
-├── README.md
-├── keymaps
-│   └── my-package.json         <- Key shortcuts registered by your package
-├── lib
-│   ├── my-package-view.js
-│   └── my-package.js           <- Entry point of your package
-├── menus
-│   └── my-package.json         <- Menus declaration of your package into Atom application
-├── package.json                <- Description and library dependencies of your package
-├── spec                        <- Tests directory (Jasmine) of your package
-│   ├── my-package-spec.js
-│   └── my-package-view-spec.js
-└── styles                      <- Stylesheets used by your package
-└── my-package.less
-

The first element to add to your package is the package.json  file which has to contain all information of your package such as its name, version, license type, keywords that will enable you to find your package into Atom registry and also your package dependancies.

-

Please also note that there is a section called activationCommands  which allows to define the executed command when your package is loaded.

-

Next, we have the keymaps/my-package.json  file which allows you to define shortcuts into your package very easily. Here is the default example:

-
{
-  "atom-workspace": {
-    "ctrl-alt-p": "my-package:toggle"
-  }
-}
-

Next, we will go into your package entry point. It is located into lib/my-package.js file.

-

This file exports a default object which contains a subscriptions  property and also activate()  and deactivate()  methods.

-

During package activation (inside activate() method), we will register a CompositeDisposable type object inside our subscriptions  property and that will allow us to add and maybe later remove some commands offered by our package:

-
activate(state) {
-  this.subscriptions = new CompositeDisposable();
-  this.subscriptions.add(atom.commands.add('atom-workspace', {
-    'my-package:toggle': () => this.toggle()
-  }));
-}
-

Now that our command is registered, we can test it by simply typing the following words, into the Atom command palette: My Package: Toggle .
-This command will execute the code contained in the toggle()  method of the class and will display a little modal at the top of the window.

-

You can add as many commands as you want and I really encourage you to decouple your code.

-

 

-

Add settings for your package

-

The Config component allows your package to have some settings.

-

To add a new setting, you just have to define a config  property into your package's class which is an object containing each settings definition, as follows:

-
config: {
-  "gitlabUrl": {
-    "description": "If you rely on a private Gitlab server, please type your base URI here (default: https://gitlab.com).",
-    "type": "string",
-    "default": "https://gitlab.com"
-  }
-}
-

Atom settings allow multiple setting types (boolean , color , integer , string , ...) so it can fit your needs on setting values by your users.

-

Once it is added, if you reload your package, you will see your package settings appearing into Atom settings:

-

Atom - Settings
-

-

In order to retrieve the value (or default value) defined by a user for a given setting in your code, you just have to use the following line:

-
let gitlabUrl = atom.config.get('gitlabUrl');
-
-

-

Components overview

-

So you are now ready to develop your package. We will have a quick overview of some interesting components that Atom brings to you and allows you to use in your package.

-

TextEditor: Interact with the text editor

-

With the TextEditor component, you will be able to insert some text into user's text editor, to save the current file, to go back and forth the history, to move the cursor into editor, to copy/paste into clipboard, to play with line indentation, to scroll, and to do so much more...

-

Here are some examples to insert text in a specific position and to save the file automatically:

-
editor.setCursorBufferPosition([row, column]);
-editor.insertText('foo');
-editor.save();
-

ViewRegistry & View: Create and display your own window

-

These components allow you to create views (modals / windows) inside Atom and display them.
-You have an example of a modal View into the default package:

-
export default class MyPackageView {
-  constructor(serializedState) {
-    // Create root element
-    this.element = document.createElement('div');
-    this.element.classList.add('my-package');
-
-    // Create message element
-    const message = document.createElement('div');
-    message.textContent = 'The MyPackage package is Alive! It
-

NotificationManager & Notification: Alert your users with notifications

-

Your package can also display a variety of notifications from "success" to "fatal error":

-
atom.notifications.addSuccess('My success notification');
-atom.notifications.addInfo('My info notification');
-atom.notifications.addWarning('My warning notification');
-atom.notifications.addError('My error notification');
-atom.notifications.addFatalError('My fatal error notification');
-

GitRepository

-

This one is also really interesting: indeed, you can access all the git properties of the current git repository that is used.

-

This way, you will be able to access the current branch name, the repository remote URL and also see if a file is considered as a new or modified file. Let's see it in action:

-
let repository = atom.project.getRepositoryForDirectory('/path/to/project');
-
-console.log(repository.getOriginURL());               // -> git@github.com:eko/atom-pull-request.git
-console.log(repository.getShortHead());               // -> master
-console.log(repository.isStatusNew('/path/to/file')); // -> true
-

And more things to discover...

-

We just made a review of the components that I played with but I invite you to read more on the following link if you want to go further: https://atom.io/docs/api/latest/AtomEnvironment

-

 

-

Test your package with specs

-

Our package is now developed but we don't have to forget about the tests. To do that, Atom uses Jasmine.

-

Your default package already has a prepared test file:

-
import MyPackageView from '../lib/my-package-view';
-
-describe('MyPackageView', () => {
-  it('has one valid test', () => {
-    expect('life').toBe('easy');
-  });
-});
-

Jasmine specs tests are written in the following way:

- -

This is now your turn to play with Jasmine and test your package logic.

-

In order to run the specs tests, you just have to navigate into the following menu: View  -> Packages  -> Run Package Specs .

-

 

-

Publish your package

-

Our package is now ready to be deployed! Let's send it.

-

Atom - Fusée

-

To do that, we will use the apm  CLI tool which comes with Atom when installing it.
-After pushing your code into a Github repository, simply go into your package directory and type the following command:

-
$ apm publish --tag v0.0.1 minor
-
-Preparing and tagging a new version ✓
-Pushing v0.0.1 tag ✓
-...
-

This command will be in charge of creating the new version tag into repository and publish this version into the Atom registry.

-

Congratulations, your package is now published and available on the following URL: https://atom.io/packages/<your-package>!

-

 

-

Continuous Integration

-

The final step is to ensure that your package will continue to work in the future when you or your contributors will add new features but also when Atom releases a new beta version. To do that, you can use Travis-CI on your repository with the following configuration:

-
language: objective-c
-
-notifications:
-  email:
-    on_success: never
-    on_failure: change
-
-script: 'curl -s https://raw.githubusercontent.com/nikhilkalige/docblockr/develop/spec/atom-build-package.sh | sh'
-
-env:
-  global:
-    - APM_TEST_PACKAGES=""
-
-  matrix:
-    - ATOM_CHANNEL=stable
-    - ATOM_CHANNEL=beta
-

-

Conclusion

-

I personally think that this is a little revolution to allow developers to make their own editor and bring the features they want.

-

Moreover, the Atom API is already very rich and very simple to use and this is certainly the main reason why the community offers a large number of packages.

-

To conclude, as for all libraries, it is not useful to reinvent the wheel by creating already existing packages. The idea is to add features if they don't already exists, in order to enrich your user experience.

-s ALIVE!'; - message.classList.add('message'); - this.element.appendChild(message); - } - - // ... -} - -let myPackageView = new MyPackageView(state.myPackageViewState); -let modalPanel = atom.workspace.addModalPanel({ - item: myPackageView.getElement(), - visible: false; -}); - -modalPanel.show();
-

NotificationManager & Notification: Alert your users with notifications

-

Your package can also display a variety of notifications from "success" to "fatal error":

-
atom.notifications.addSuccess('My success notification');
-atom.notifications.addInfo('My info notification');
-atom.notifications.addWarning('My warning notification');
-atom.notifications.addError('My error notification');
-atom.notifications.addFatalError('My fatal error notification');
-

GitRepository

-

This one is also really interesting: indeed, you can access all the git properties of the current git repository that is used.

-

This way, you will be able to access the current branch name, the repository remote URL and also see if a file is considered as a new or modified file. Let's see it in action:

-
let repository = atom.project.getRepositoryForDirectory('/path/to/project');
-
-console.log(repository.getOriginURL());               // -> git@github.com:eko/atom-pull-request.git
-console.log(repository.getShortHead());               // -> master
-console.log(repository.isStatusNew('/path/to/file')); // -> true
-

And more things to discover...

-

We just made a review of the components that I played with but I invite you to read more on the following link if you want to go further: https://atom.io/docs/api/latest/AtomEnvironment

-

 

-

Test your package with specs

-

Our package is now developed but we don't have to forget about the tests. To do that, Atom uses Jasmine.

-

Your default package already has a prepared test file:

-
import MyPackageView from '../lib/my-package-view';
-
-describe('MyPackageView', () => {
-  it('has one valid test', () => {
-    expect('life').toBe('easy');
-  });
-});
-

Jasmine specs tests are written in the following way:

- -

This is now your turn to play with Jasmine and test your package logic.

-

In order to run the specs tests, you just have to navigate into the following menu: View  -> Packages  -> Run Package Specs .

-

 

-

Publish your package

-

Our package is now ready to be deployed! Let's send it.

-

Atom - Fusée

-

To do that, we will use the apm  CLI tool which comes with Atom when installing it.
-After pushing your code into a Github repository, simply go into your package directory and type the following command:

-
$ apm publish --tag v0.0.1 minor
-
-Preparing and tagging a new version ✓
-Pushing v0.0.1 tag ✓
-...
-

This command will be in charge of creating the new version tag into repository and publish this version into the Atom registry.

-

Congratulations, your package is now published and available on the following URL: https://atom.io/packages/<your-package>!

-

 

-

Continuous Integration

-

The final step is to ensure that your package will continue to work in the future when you or your contributors will add new features but also when Atom releases a new beta version. To do that, you can use Travis-CI on your repository with the following configuration:

-
language: objective-c
-
-notifications:
-  email:
-    on_success: never
-    on_failure: change
-
-script: 'curl -s https://raw.githubusercontent.com/nikhilkalige/docblockr/develop/spec/atom-build-package.sh | sh'
-
-env:
-  global:
-    - APM_TEST_PACKAGES=""
-
-  matrix:
-    - ATOM_CHANNEL=stable
-    - ATOM_CHANNEL=beta
-

-

Conclusion

-

I personally think that this is a little revolution to allow developers to make their own editor and bring the features they want.

-

Moreover, the Atom API is already very rich and very simple to use and this is certainly the main reason why the community offers a large number of packages.

-

To conclude, as for all libraries, it is not useful to reinvent the wheel by creating already existing packages. The idea is to add features if they don't already exists, in order to enrich your user experience.

-{% endraw %} diff --git a/_drafts/2017-01-17-redux-structurez-vos-applications-front.html b/_drafts/2017-01-17-redux-structurez-vos-applications-front.html deleted file mode 100644 index 5b90eeddb..000000000 --- a/_drafts/2017-01-17-redux-structurez-vos-applications-front.html +++ /dev/null @@ -1,212 +0,0 @@ ---- -layout: post -title: 'Redux : Structurez vos applications front' -author: vcomposieux -date: '2017-01-17 10:09:00 +0100' -date_gmt: '2017-01-17 09:09:00 +0100' -categories: -- Non classé -- Javascript -tags: -- Javascript -- react -- redux -- vuejs ---- -{% raw %} -

L'écosystème Javascript est très riche, beaucoup de développeurs mais aussi de frameworks et d'outils sont disponibles.

-

Lorsque vous souhaitez développer une application, quel que soit son framework de rendu, vous allez vite être amené à vouloir architecturer votre projet afin de différencier et d'organiser les données des vues. C'est particulièrement le cas lorsque vous utilisez des frameworks de rendu de composants comme React ou VueJS.

-

Historiquement, le besoin s'est fait sentir sur React et Facebook a donc ouvert les sources de son outil Flux.

-

Le principe est le suivant :

-

-

Votre application déclare, pour chaque composant, les actions  qui lui sont liées. Ces actions permettent de définir l'état de votre composant, stocké dans un store , qui permet de maintenir votre vue  à jour.

-

L'inconvénient est que dans ce cas, vous avez un store par composant. Ce modèle fonctionne pour React mais vous pouvez vous sentir limité sur certaines applications.

-

Dan Abramov a donc lancé, en juin 2015, Redux, qui permet principalement de simplifier la gestion du store car il y a en effet qu'un seul store pour toute votre application dans Redux.

-

Tous vos composants peuvent donc accéder à vos données.

-

Pour plus d'informations sur les différences Redux / Flux, je vous invite à lire cette réponse de Dan.

-

 

-

Installation

-

Nous allons voir dans cet article comment mettre en place et utiliser Redux sur vos projets.
-Notez dès maintenant que la librairie peut être utilisée avec plusieurs librairies de rendu comme React ou VueJS.

-

Pour installer Redux, il vous faudra installer le package npm (ou yarn) redux .
-Si vous utilisez Redux sur une application React, il vous faudra également le package react-redux  ou encore vue-redux  s'il s'agit d'un projet VueJS.

-
$ yarn add redux
-

Rien de plus, vous êtes prêt à utiliser Redux.

-

 

-

Utilisation classique

-

Comme décrit précédemment, il vous faudra initialiser un store  qui va permettre de stocker l'état de votre application.

-

Pour instancier ce store, il vous faudra passer un ou plusieurs reducers . Les reducers contiennent les méthodes qui effectuent le changement d'état de votre application.
-Ces changements d'état sont effectués lorsqu'une action  est déclenchée sur votre application.

-

Voilà, nous avons là les 3 composantes d'une application structurée par Redux : des actions, des reducers et un store.

-

Nous allons prendre un cas pratique simple : un compteur que l'on peut incrémenter ou décrémenter d'une certaine valeur.

-

 

-

Voici l'arborescence que nous ciblons :

-
src/
-├── actions
-│   └── counter.js
-├── constants
-│   └── ActionTypes.js
-├── reducers
-│   ├── another.js
-│   ├── counter.js
-│   └── index.js
-└── store
-    └── configureStore.js
-

 

-

Actions

-

Écrivons donc un fichier d'actions qui permet de définir ces deux actions : incrémenter et décrémenter.

-

Avant tout, nous allons également stocker ces noms d'actions dans des constantes, ce qui nous permettra d'être clair dans notre code car nous ferons toujours appel à ces constantes.

-

Créez donc un fichier src/constants/ActionTypes.js  avec le contenu :

-
export const INCREMENT = 'INCREMENT';
-export const DECREMENT = 'DECREMENT';
-

 

-

Nous allons maintenant écrire les définitions des actions. Créez maintenant le fichier src/actions/counter.js  :

-
import * as types from '../constants/ActionTypes';
-
-export const increment = (value) => ({ type: types.INCREMENT, value });
-export const decrement = (value) => ({ type: types.DECREMENT, value });
-

 

-

Vous venez de déclarer deux actions (increment  et decrement ) qui prennent chacune un type (obligatoire) et une valeur à ajouter ou soustraire.

-

 

-

Reducers

-

Il nous faut maintenant écrire les méthodes des reducers permettant de mettre à jour l'état de notre application.

-

Ces reducers seront écrits dans le fichier src/reducers/counter.js  :

-
import { INCREMENT, DECREMENT } from '../constants/ActionTypes';
-
-const initialState = {
-  current: 0,
-};
-
-export default function counter(state = initialState, action) {
-  switch (action.type) {
-    case INCREMENT:
-      return {
-        current: state.current += action.value,
-      };
-
-    case DECREMENT:
-      return {
-        current: state.current -= action.value,
-      };
-
-    default:
-      return state;
-  }
-}
-

 

-

 

-

Vous avez compris l'idée, nous avons nos actions dans un switch() { case ... }  et mettons directement à jour les valeurs de notre store.
-Vous remarquerez que nous avons créés un état initial (initialState) afin d'initialiser les valeurs de notre application.

-

[note]Note : Il vous est possible de créer autant de reducers que nécessaire.[/note]

-

 

-

Si vous avez déclaré plusieurs reducers dans votre application, vous pouvez les combiner dans un fichier src/reducers/index.js  comme suit :

-

 

-
import { combineReducers } from 'redux';
-
-import counter from './counter';
-import another from './another';
-
-const reducers = combineReducers({
-  counter,
-  another,
-});
-
-export default reducers;
-

 

-

Store

-

Maintenant que nous avons nos actions et reducers, dernière étape indispensable : la création du store !

-

Créez un fichier src/store/configureStore.js  avec le contenu suivant :

-
import { createStore } from 'redux';
-import reducers from '../reducers';
-
-const configureStore = () => {
-  return createStore(
-    reducers,
-  );
-};
-
-export default configureStore;
-

 

-

Nous utilisons ici la fonction createStore()  de l'API Redux permettant de créer notre store.

-

 

-

Afin d'aller un peu plus loin, notez que cette fonction peut prendre jusqu'à 3 arguments :

-
    -
  1. un ou des reducers,
  2. -
  3. un état pré-chargé (optionnels), correspondant à un état initial,
  4. -
  5. des "enhancers" (optionnels), autrement dit des callbacks comme des middlewares.
  6. -
-

Un middleware permet d'exécuter une callback à chaque fois que le dispatch()  d'actions est exécuté.

-

 

-

Voici un exemple de middleware permettant de logger chaque action déclenchée :

-
import { createStore, applyMiddleware } from 'redux'
-import reducers from '../reducers';
-
-function logger({ getState }) {
-  return (next) => (action) => {
-    console.log('will dispatch', action)
-    return next(action)
-  }
-}
-
-const configureStore = () => {
-  return createStore(
-    reducers,
-    applyMiddleware(logger)
-  );
-};
-
-export default configureStore;
-

 

-

N'oubliez pas d'utiliser la fonction applyMiddleware()  lorsque vous passez vos fonctions de middleware au store.

-

 

-

Utilisation avec React

-

Le principe reste exactement le même lorsque Redux est utilisé avec React, cependant, la librairie react-redux  va vous apporter des petites choses en plus.

-

Vous allez en effet pouvoir lier l'état de votre application gérée par Redux ainsi que les actions que vous avez définies avec les props  de vos composants React.

-

Prenons un composant Counter  reflétant l'architecture Redux mise en place dans notre cas d'exemple :

-
import React, { PropTypes } from 'react';
-import { connect } from 'react-redux';
-import { bindActionCreators } from 'redux';
-
-import * as CounterActions from '../actions/counter';
-
-const Counter = ({ children, value, actions }) => (
-  
- - - - -
-); - -Counter.propTypes = { - children: PropTypes.object.isRequired, - value: PropTypes.number.isRequired, - actions: PropTypes.object.isRequired, -}; - -const mapStateToProps = state => ({ - value: state.counter.current, -}); - -const mapDispatchToProps = dispatch => ({ - actions: bindActionCreators(CounterActions, dispatch), -}); - -export default connect( - mapStateToProps, - mapDispatchToProps, -)(Counter);
-

 

-

De cette façon, nous récupérons donc les valeurs de nos props provenant de notre store mais également une propriété actions  permettant d'appeler nos actions Redux.

-

Les principaux éléments à noter ici sont :

- -

Ces deux fonctions sont ensuite appliquées à l'aide de la fonction connect()  fournie par react-redux .

-

[note]Note : Nous devons ici utiliser bindActionCreators()  sur nos CounterActions  car il s'agit d'un objet dont les valeurs sont des actions et cette fonction va permettre d'ajouter un appel à la fonction dispatch()  de Redux afin que celles-ci soient correctement déclenchées.[/note]

-

 

-

Conclusion

-

Si nous mettons en parallèle les 1 303 720 téléchargements sur le mois précédent de la librairie Redux avec les 2 334 221 de téléchargements pour React, nous remarquons que Redux est aujourd'hui très utilisé et semble vraiment très apprécié par les développeurs car il s'agit d'une solution simple qui permet réellement de structurer une application front.

-

Redux apporte, selon moi, une vraie solution permettant de structurer des applications au métier complexe aux communautés comme React, VueJS mais également aux autres.

-{% endraw %} diff --git a/_drafts/2017-01-20-redux-structure-frontend-applications.html b/_drafts/2017-01-20-redux-structure-frontend-applications.html deleted file mode 100644 index d543d9f93..000000000 --- a/_drafts/2017-01-20-redux-structure-frontend-applications.html +++ /dev/null @@ -1,196 +0,0 @@ ---- -layout: post -title: 'Redux: Structure your frontend applications' -author: vcomposieux -date: '2017-01-20 12:12:34 +0100' -date_gmt: '2017-01-20 11:12:34 +0100' -categories: -- Non classé -tags: -- Facebook -- Javascript -- react -- redux ---- -{% raw %} -

Javascript ecosystem is really rich: full of developers but also full of frameworks and libraries.

-

When you want to develop a frontend application, whatever its rendering framework, you will have to structure things into your project in order to organize the data management with views. This case occurs particularly when you use component rendering frameworks like React or VueJS.

-

Historically, this has been needed by React so that's why Facebook has open sourced its tool named Flux.

-

Here is the philosophy:

-

-

Your application declare actions  for each components. These actions allow you to define the state of your data which is stored in a store . This stores continually maintains your view  up-to-date.

-

We have a drawback in this case because you have to define one store per component. This is working but on large applications you can feel limited with it.

-

In June 2015, Dan Abramov has launched Redux which simplify store management because you only have one store for all your application.

-

All of your application components can access to the whole state.

-

For more information about Redux/Flux differences I encourage you to have a look at Dan's answer on this subject.

-

 

-

Installation

-

This article will deal about how to install and use Redux on your own projects.
-Please keep in mind that Redux can be used with multiple rendering frameworks like React or VueJS.

-

To install Redux, you will just need the redux npm (or yarn) package.
-If you use Redux into a React application, you will also need the react-redux  package or even the vue-redux  if you want to use it on a VueJS project.

-
$ yarn add redux
-

Nothing more, you can now start to use Redux.

-

 

-

Basic usage

-

As previously described, you will have to instanciate a new store  that will allow to store the state of all your application.

-

In order to instanciate this store, you will have to give to it some reducers . Reducers contain methods that change the state of your application.
-These state changes occur when an action  is dispatched by your application.

-

Here we are, we have the 3 things needed by a Redux application: actions, reducers and a store.

-

We will use a simple practical case: a counter that we can increment or decrement with a given value.

-

Here is our target arborescence:

-
src/
-├── actions
-│   └── counter.js
-├── constants
-│   └── ActionTypes.js
-├── reducers
-│   ├── another.js
-│   ├── counter.js
-│   └── index.js
-└── store
-    └── configureStore.js
-

Actions

-

Let's write an actions containing file that will implement our 2 actions: increment and decrement.

-

Before all, we will store these actions names into constants in order to keep our code clear and comprehensible as we will always call these constants in all of our code.

-

Start by creating a src/constants/ActionTypes.js  file with the following content:

-
export const INCREMENT = 'INCREMENT';
-export const DECREMENT = 'DECREMENT';
-

Great, we will now write actions that correspond to these constants in a src/actions/counter.js  file:

-
import * as types from '../constants/ActionTypes';
-
-export const increment = (value) => ({ type: types.INCREMENT, value });
-export const decrement = (value) => ({ type: types.DECREMENT, value });
-

You have just created your 2 actions (increment  and decrement) which each have a type property (required) and a value to add or remove to the current counter value.

-

Reducers

-

We will now write reducers functions that correspond to the actions we previously wrote in order to update the value in our application state.

-

This will be written in the src/reducers/counter.js  file:

-
import { INCREMENT, DECREMENT } from '../constants/ActionTypes';
-
-const initialState = {
-  current: 0,
-};
-
-export default function counter(state = initialState, action) {
-  switch (action.type) {
-    case INCREMENT:
-      return {
-        current: state.current += action.value,
-      };
-
-    case DECREMENT:
-      return {
-        current: state.current -= action.value,
-      };
-
-    default:
-      return state;
-  }
-}
-

You got the idea, we have our actions wrapped into a switch() { case ... }  and directly return the store updated with new values.
-You can also observe that we have initialized an initial state (initialState) in order to prepare our application state with some default values.

-

[note]Note: You can write as many reducers as you need in your application so you can clearly split your code application.[/note]

-

Only point if you declare multiple reducers into your application is that you will have to combine them here in a file named src/reducers/index.js  as follows:

-
import { combineReducers } from 'redux';
-
-import counter from './counter';
-import another from './another';
-
-const reducers = combineReducers({
-  counter,
-  another,
-});
-
-export default reducers;
-

Store

-

You have your actions and your reducers so let's dive into the final step: store creation!

-

Store will be created in a src/store/configureStore.js  file with only these couple of lines:

-
import { createStore } from 'redux';
-import reducers from '../reducers';
-
-const configureStore = () => {
-  return createStore(
-    reducers,
-  );
-};
-
-export default configureStore;
-

You just have to call the Redux's createStore()  API function in order to create your store.

-

In order to go further, please note that this function can take a maximum of 3 arguments:

-
    -
  1. one or many combines reducers,
  2. -
  3. a pre-loaded state (optional), corresponding to an initial state,
  4. -
  5. some "enhancers" (optionals), which are some callbacks such as middlewares.
  6. -
-

A middleware is a callback that is executed each time Redux can the dispatch()  function so each time an action is triggered.

-

Here is a simple middleware that logs each dispatched actions:

-
import { createStore, applyMiddleware } from 'redux'
-import reducers from '../reducers';
-
-function logger({ getState }) {
-  return (next) => (action) => {
-    console.log('will dispatch', action)
-    return next(action)
-  }
-}
-
-const configureStore = () => {
-  return createStore(
-    reducers,
-    applyMiddleware(logger)
-  );
-};
-
-export default configureStore;
-

Do not forget to call the applyMiddleware()  function when you pass your function to the store argument.

-

 

-

React use case

-

Principles are exactly the same when you want to use Redux on a React application. However, the react-redux  library brings some cool additional features to fit with React.

-

Indeed, thanks to this library, you will be able to map your React components props  with the Redux state and actions.

-

Let's take a concrete case: a Counter  component which could be a component for our previous use case:

-
import React, { PropTypes } from 'react';
-import { connect } from 'react-redux';
-import { bindActionCreators } from 'redux';
-
-import * as CounterActions from '../actions/counter';
-
-const Counter = ({ children, value, actions }) => (
-  
- - - - -
-); - -Counter.propTypes = { - children: PropTypes.object.isRequired, - value: PropTypes.number.isRequired, - actions: PropTypes.object.isRequired, -}; - -const mapStateToProps = state => ({ - value: state.counter.current, -}); - -const mapDispatchToProps = dispatch => ({ - actions: bindActionCreators(CounterActions, dispatch), -}); - -export default connect( - mapStateToProps, - mapDispatchToProps, -)(Counter);
-

This way, we are able to retrieve our props values which came from the Redux store but also an actions  property that will allow us to dispatch Redux events when we will call it.

-

Main things to note here are:

- -

These two functions are applied thanks to the connect()  function brought by the react-redux library.

-

[note]Note: We have to use the bindActionCreators()  function over our CounterActions  because this is an object that contains actions functions so this function will allows React to call the dispatch()  Redux function when React will call the functions in order to have them correctly triggered.[/note]

-

 

-

Conclusion

-

If we put in parallel the download numbers of Redux (1 303 720 download over the previous month) with the 2 334 221 downloads of React, we can conclude that Redux is today very used and seems very much appreciated by developers because it's a simple solution that can greatly help you to structure your application.

-

Redux brings, in my opinion, a real solution to structure complex (or large) business applications and bring that to the React and VueJS (and others) communities.

-{% endraw %} diff --git a/_drafts/2017-02-22-consul-service-discovery-failure-detection-2.html b/_drafts/2017-02-22-consul-service-discovery-failure-detection-2.html deleted file mode 100644 index f781e473b..000000000 --- a/_drafts/2017-02-22-consul-service-discovery-failure-detection-2.html +++ /dev/null @@ -1,206 +0,0 @@ ---- -layout: post -title: 'Consul : Service Discovery et Failure Detection' -author: vcomposieux -date: '2017-02-22 10:49:25 +0100' -date_gmt: '2017-02-22 09:49:25 +0100' -categories: -- Dev Ops -- Devops -tags: -- service -- consul -- discovery -- failure -- detection -- health -- check ---- -{% raw %} -

Introduction

-

Consul est un outil développé en Go par la société HashiCorp et a vu le jour en 2013.
-Consul a plusieurs composants mais son objectif principal est de regrouper la connaissance des services d'une architecture (service discovery) et permet de s'assurer que les services contactés sont toujours disponibles en s'assurant que la santé de ces services est toujours bonne (via du health check).

-

 

-

Concrètement, Consul va nous apporter un serveur DNS permettant de mettre à jour les adresses IP disponibles pour un service, en fonction de ceux qui sont en bonne santé. Ceci permet également de faire du load balancing bien que nous verrons qu'il ne permette pas pour le moment de préférer un service à un autre.

-

Il offre également d'autres services tel que du stockage clé/valeur, nous l'utiliserons dans cet article afin que Docker Swarm y stocke ses valeurs.

-

 

-

Afin de clarifier la suite de cet article, voici les ports utilisés par Consul :

- -

 

-

La suite de cet article va se concentrer sur la partie service discovery et failure detection. Nous allons pour cela mettre en place un cluster Docker Swarm possédant l'architecture suivante :

-

-

Nous aurons donc 3 machines Docker :

- -

 

-

Nous mettrons également sur nos deux nodes (cluster Docker Swarm) un container Docker pour Registrator, permettant de faciliter l'enregistrement de nos services Docker sur Consul.

-

Pour plus d'informations concernant Registrator, vous pouvez vous rendre sur : https://gliderlabs.com/registrator/

-

Commençons à installer notre architecture !

-

 

-

Service discovery

-

 

-

Première machine : Consul (Swarm Discovery)

-

Nous allons commencer par créer la première machine : notre Consul.

-

 

-

Pour cela, tapez :

-
$ docker-machine create -d virtualbox consul
-

 

-

Une fois la machine prête, préparez votre environnement pour utiliser cette machine et lancez un container Consul :

-
$ eval $(docker-machine env consul)
-$ docker run -d \
-    -p 8301:8301 \
-    -p 8302:8302 \
-    -p 8400:8400 \
-    -p 8500:8500 \
-    -p 53:8600/udp \
-    consul
-

 

-

Nous avons maintenant notre Consul prêt à recevoir nos services et nos prochaines machines membres de notre cluster Docker Swarm.

-

Vous pouvez d'ailleurs ouvrir l'interface web mise à disposition en obtenant l'ip de la machine Consul :

-
$ docker-machine ip consul
-<ip-obtenue>
-

 

-

Puis ouvrez dans votre navigateur l'URL : http://<ip-obtenue>:8500.

-

 

-

Deuxième machine : Node 01

-

Nous allons maintenant créer la machine correspondant au premier node de notre cluster Docker Swarm qui se verra également obtenir le rôle de master de notre cluster Swarm (il en faut bien un).

-
$ docker-machine create -d virtualbox \
-    --swarm \
-    --swarm-master \
-    --swarm-discovery="consul://$(docker-machine ip consul):8500" \
-    --engine-opt="cluster-store=consul://$(docker-machine ip consul):8500" \
-    --engine-opt="cluster-advertise=eth1:2376" swarm-node-01
-

 

-

Comme vous le voyez, nous précisons l'option --swarm-discovery  avec l'IP de notre machine Consul et le port 8500 correspondant à l'API de Consul. Ainsi, Docker Swarm pourra utiliser l'API pour enregistrer les machines du cluster.

-

 

-

Nous allons maintenant configurer notre environnement pour utiliser cette machine et y installer dessus un container Registrator permettant d'auto-enregistrer les nouveaux services sur Consul.

-

 

-

Pour ce faire, tapez :

-
$ eval $(docker-machine env swarm-node-01)
-

puis :

-
$ docker run -d \
-    --volume=/var/run/docker.sock:/tmp/docker.sock \
-    gliderlabs/registrator \
-    -ip $(docker-machine ip swarm-node-01) \
-    consul://$(docker-machine ip consul):8500
-

 

-

Vous remarquez que nous partageons le socket Docker sur la machine. Cette solution peut être controversée mais dans le cas de cet article, passons là-dessus. Pour une architecture stable, nous préférerons enregistrer nous-même les services via l'API de Consul.
-L'option -ip  permet de préciser à Registrator l'IP sur laquelle nous voulons accéder aux services, à savoir l'IP de la machine et non pas l'IP interne du container Docker.

-

Nous sommes prêts à démarrer notre service HTTP. Celui-ci est une simple image Docker "ekofr/http-ip" qui lance une application HTTP écrite en Go et qui affiche "hello, <ip>" avec l'adresse IP du container courant.

-

 

-

Pour le besoin de cet article, nous allons également créer un réseau différent entre les deux machines afin d'identifier des adresses IP différentes pour les deux services.

-

 

-

Créons donc un nouveau réseau pour notre node 01 :

-
$ docker network create \
-    --subnet=172.18.0.0/16 network-node-01
-

puis utilisez ce réseau sur le container du service HTTP :

-
$ docker run -d \
-    --net network-node-01 \
-    -p 80:8080 \
-    ekofr/http-ip
-

 

-

Avant d'exécuter les mêmes étapes pour créer notre node 02, assurons-nous d'avoir un service fonctionnel :

-
$ curl http://localhost:80
-hello from 172.18.0.X
-

 

-

Troisième machine : Node 02

-

Nous allons donc (presque) répéter les étapes du node 01 en changeant quelques valeurs seulement. Créez la machine :

-
$ docker-machine create -d virtualbox \
-    --swarm \
-    --swarm-discovery="consul://$(docker-machine ip consul):8500" \
-    --engine-opt="cluster-store=consul://$(docker-machine ip consul):8500" \
-    --engine-opt="cluster-advertise=eth1:2376" swarm-node-02
-

 

-

Préparez votre environnement pour utiliser cette machine node 02 et installez-y Registrator :

-
$ eval $(docker-machine env swarm-node-02)
-
$ docker run -d \
-    --volume=/var/run/docker.sock:/tmp/docker.sock \
-    gliderlabs/registrator \
-    -ip $(docker-machine ip swarm-node-02) \
-    consul://$(docker-machine ip consul):8500
-

 

-

Puis créez un nouveau réseau et lancez le service HTTP avec ce réseau :

-
$ docker network create \
-     --subnet=172.19.0.0/16 network-node-02
-
$ docker run -d \
-    --net network-node-02 \
-    -p 80:8080 \
-    ekofr/http-ip
-

Nous voilà prêt à découvrir ce que nous apporte Consul.

-

 

-

Requêtes DNS

-

Vous pouvez en effet maintenant résoudre votre service http-ip.service.consul  en utilisant le serveur DNS apporté par Consul, vous devriez voir vos deux services enregistrés :

-
$ dig @$(docker-machine ip consul) http-ip.service.consul
-
-;; QUESTION SECTION:
-;http-ip.service.consul. IN A
-
-;; ANSWER SECTION:
-http-ip.service.consul. 0 IN A 192.168.99.100
-http-ip.service.consul. 0 IN A 192.168.99.102
-

 

-

Autrement dit, un load balancing sera fait sur un de ces deux services lorsque vous chercherez à joindre http://http-ip.service.consul .

-

Oui, mais qu'en est-il du côté de la répartition de cette charge ? Pouvons-nous définir une priorité et/ou poids ?
-Malheureusement, la réponse est non, pas pour le moment. Une issue a cependant été ouverte sur Github pour demander le support de celui-ci : https://github.com/hashicorp/consul/issues/1088.

-

 

-

En effet, si nous regardons de plus près l'enregistrement DNS de type SRV , voici ce que nous obtenons :

-
$ dig @$(docker-machine ip consul) http-ip.service.consul SRV
-
-;; ANSWER SECTION:
-http-ip.service.consul. 0 IN SRV 1 1 80 c0a86366.addr.dc1.consul.
-http-ip.service.consul. 0 IN SRV 1 1 80 c0a86364.addr.dc1.consul.
-

 

-

Comme vous pouvez le voir, la priorité et le poids sont tous les deux définis à 1, le load balancing sera donc équilibré entre tous les services.

-

Si vous ajoutez l'IP de la machine Consul en tant que serveur DNS sur votre système d'exploitation, vous pourrez donc appeler votre service en HTTP et vous rendre compte plus facilement du load balancing :

-
$ curl http://http-ip.service.consul
-hello from 172.18.0.2
-
-$ curl http://http-ip.service.consul
-hello from 172.19.0.2
-

 

-

Nous avons ici une IP correspondant à chaque service HTTP que nous avons enregistré.

-

 

-

Failure detection

-

Nous allons maintenant ajouter un Health Check à notre service afin de s'assurer que celui-ci peut être utilisé.

-

Nous allons donc commencer par retourner sur notre node 01 et supprimer le container ekofr/http-ip  afin de le recréer avec un Health Check :

-
$ eval $(docker-machine env swarm-node-01)
-
$ docker kill \
-$(docker ps -q --filter='ancestor=ekofr/http-ip')
-

 

-

Registrator nous offre des variables d'environnement afin d'ajouter des Health Check de nos containers à Consul, vous pouvez consulter la liste de toutes les variables disponibles ici : http://gliderlabs.com/registrator/latest/user/backends/#consul.

-

 

-

L'idée est pour nous de vérifier que le port 80 répond correctement, nous allons donc ajouter un script exécutant simplement une requête curl. Pour ce faire :

-
$ docker run -d \
-    --net network-node-01 -p 80:8080 \
-    -e SERVICE_CHECK_SCRIPT="curl -s -f http://$(docker-machine ip swarm-node-01)" \
-    -e SERVICE_CHECK_INTERVAL=5s \
-    -e SERVICE_CHECK_TIMEOUT=1s \
-    ekofr/http-ip
-

 

-

Vous pouvez faire de même sur le node 02 (en faisant attention à bien modifier les node-01  en node-02 ) et vous devriez maintenant pouvoir visualiser ces checks sur l'interface Consul :

-

-

 

-

De la même façon, vous pouvez également utiliser l'API de Consul afin de vérifier la santé de vos services :

-
$ curl http://$(docker-machine ip consul):8500/v1/health/checks/http-ip ..
-[
-  {
-    "Status": "passing",
-    "Output": "hello from 172.18.0.2",
-    "ServiceName": "http-ip",
-  },
-  ...
-]
-

 

-

Conclusion

-

Vous pouvez maintenant mettre en place Consul sur vos architectures afin de vous assurer que les services contactés sont bien disponibles mais surtout pouvoir identifier les éventuels problèmes qui peuvent survenir sur vos services.

-

Il est donc important d'ajouter un maximum de checks sur les éléments pouvant rendre vos services indisponibles (vérifier que celui-ci peut bien être contacté, vérifier l'espace disque disponible sur la machine, etc ...).

-

Consul est un outil qui s'intègre parfaitement dans vos architectures, grâce à son utilisation très simple et son API complète.

-{% endraw %} diff --git a/_drafts/2017-02-22-consul-service-discovery-failure-detection.html b/_drafts/2017-02-22-consul-service-discovery-failure-detection.html deleted file mode 100644 index d08b4674e..000000000 --- a/_drafts/2017-02-22-consul-service-discovery-failure-detection.html +++ /dev/null @@ -1,193 +0,0 @@ ---- -layout: post -title: 'Consul: Service Discovery and Failure Detection' -author: vcomposieux -date: '2017-02-22 10:50:16 +0100' -date_gmt: '2017-02-22 09:50:16 +0100' -categories: -- Non classé -tags: [] ---- -{% raw %} -

Introduction

-

Consul is a product developed in Go language by the HashiCorp company and was born in 2013.
-Consul has multiple components but its main goal is to manage the services knowledge in an architecture (which is service discovery) and also allows to ensure that all contacted services are always available by adding health checks on them.

-

Basically, Consul will bring us a DNS server that will resolve IP addresses of a host's services, depending on which one will be healthy.

-

 

-

This method also allows to do load balancing even if we will see in this blog post that it's actually not possible to distribute priorities between services.

-

 

-

Another Consul service we'll use in this article is key/value storage because we'll create a Docker Swarm cluster and will use Consul as the discovery/storage for Docker Swarm.

-

In order to clarify the rest of the article, here are the ports used by Consul:

- -

 

-

Next, we'll focus on service discovery and failure detection. To do that, we'll create a Docker Swarm cluster with the following architecture:

-

-

As you can see, we'll have 3 Docker machines:

- -

 

-

We'll also add on our two nodes a Docker container with Registrator image that will facilitate the discovery of Docker containers into Consul.

-

For more information about Registrator, you can visit: https://gliderlabs.com/registrator/.

-

Let's start to install our architecture!

-

 

-

Service discovery

-

First machine: Consul (Swarm Discovery)

-

We'll start by creating our first machine: Consul

-

To do that, just type:

-
$ docker-machine create -d virtualbox consul
-

 

-

Once the machine is fully ready, prepare your environment to use this Docker machine and launch a Consul container:

-
$ eval $(docker-machine env consul)
-$ docker run -d \
-    -p 8301:8301 \
-    -p 8302:8302 \
-    -p 8400:8400 \
-    -p 8500:8500 \
-    -p 53:8600/udp \
-    consul
-

 

-

Well, your Consul is ready to receive your services and also our Docker Swarm nodes!

-

By the way, you can open the web interface by obtaining the Consul machine IP address:

-
$ docker-machine ip consul
-<obtained-ip>
-

 

-

Then, open in your browser the following URL: http://<obtained-ip>:8500 .

-

 

-

Second machine: Node 01

-

Now, it's time to create the machine that corresponds to our first Docker Swarm cluster node and that will also receive the master role for our cluster (we need one...):

-
$ docker-machine create -d virtualbox \
-    --swarm \
-    --swarm-master \
-    --swarm-discovery="consul://$(docker-machine ip consul):8500" \
-    --engine-opt="cluster-store=consul://$(docker-machine ip consul):8500" \
-    --engine-opt="cluster-advertise=eth1:2376" swarm-node-01
-

 

-

As you can see, we've added the --swarm-discovery  option with our Consul machine IP address and port 8500 that corresponds to the Consul API. This way, Docker Swarm will use the Consul API to store machine information with the rest of the cluster.

-

We'll now configure our environment to use this machine and install a Registrator  container on top of it in order to auto-register our new services (Docker containers).

-

To do that, type the following:

-
$ eval $(docker-machine env swarm-node-01)
-

 

-

Then:

-
$ docker run -d \
-    --volume=/var/run/docker.sock:/tmp/docker.sock \
-    gliderlabs/registrator \
-    -ip $(docker-machine ip swarm-node-01) \
-    consul://$(docker-machine ip consul):8500
-

 

-

Here, you can notice that we share the host Docker socket in the container. This solution could be a controversial solution but in our example case, forgive me about that ;)

-

If you want to register services to Consul I recommend to register them using the Consul API in order to keep control on what's added in your Consul.

-

The -ip  option allows to precise to Registrator that we want to register our services with the given IP address: so here the Docker machine IP address and not the Docker container internal IP address.

-

We are now ready to start our HTTP service. This one is located under the "ekofr/http-ip" Docker image which is a simple Go HTTP application that renders "hello, <ip>" with the IP address of the current container.

-

In order to fit this article needs, we will also create a different network for the two Docker machines in order to clearly identify IP addresses for our two services.

-

Let's create a new network concerning our first node:

-
$ docker network create \
-     --subnet=172.18.0.0/16 network-node-01
-

 

-

then you can use the newly created network to be used with your HTTP service container:

-
$ docker run -d \
-    --net network-node-01 \
-    -p 80:8080 \
-    ekofr/http-ip
-

 

-

Before executing the same steps for our second node, we will ensure that our HTTP service works:

-
$ curl http://localhost:80
-hello from 172.18.0.X
-

 

-

Third machine: Node 02

-

We'll now repeat most steps we've ran for our first node but we'll change some values. First, create the Docker machine:

-
$ docker-machine create -d virtualbox \
-    --swarm \
-    --swarm-discovery="consul://$(docker-machine ip consul):8500" \
-    --engine-opt="cluster-store=consul://$(docker-machine ip consul):8500" \
-    --engine-opt="cluster-advertise=eth1:2376" swarm-node-02
-

 

-

Prepare your environment to use this new node 02 machine and install Registrator container on it:

-
$ eval $(docker-machine env swarm-node-02)
-
$ docker run -d \
-    --volume=/var/run/docker.sock:/tmp/docker.sock \
-    gliderlabs/registrator \
-    -ip $(docker-machine ip swarm-node-02) \
-    consul://$(docker-machine ip consul):8500
-

 

-

Now, create the new network and launch your HTTP service:

-
$ docker network create \
-    --subnet=172.19.0.0/16 network-node-02
-
$ docker run -d \
-    --net network-node-02 \
-    -p 80:8080 \
-    ekofr/http-ip
-

We are all set! We can now discover what Consul brings to us.

-

 

-

DNS Request

-

Indeed, you can now resolve your service hostname `http-ip.service.consul` by using the DNS server brought by Consul and you should see your two services appearing as a DNS record:

-
$ dig @$(docker-machine ip consul) http-ip.service.consul
-
-;; QUESTION SECTION:
-;http-ip.service.consul. IN A
-
-;; ANSWER SECTION:
-http-ip.service.consul. 0 IN A 192.168.99.100
-http-ip.service.consul. 0 IN A 192.168.99.102
-

 

-

In other words, a kind of load balancing will be done on one of these services when you'll try to join them http://http-ip.service.consul .

-

Ok, but what about the load balancing repartition? Are we able to define a priority and/or weight for each services?
-Sadly, the answer is no, actually. An issue is currently opened about that on Github in order to bring this support. You can find it here: https://github.com/hashicorp/consul/issues/1088.

-

 

-

Indeed, if we look in details about SRV  DNS record type, here is what we get:

-
$ dig @$(docker-machine ip consul) http-ip.service.consul SRV
-
-;; ANSWER SECTION:
-http-ip.service.consul. 0 IN SRV 1 1 80 c0a86366.addr.dc1.consul.
-http-ip.service.consul. 0 IN SRV 1 1 80 c0a86364.addr.dc1.consul.
-

 

-

As you can see here, priority and weight are both defined to value 1 so the load balancing will be equal between all services.

-

If you add the Consul Docker machine IP address as a DNS server on your operating system, you'll be able to perform HTTP calls and see more easily what's happening on load balancing:

-
$ curl http://http-ip.service.consul
-hello from 172.18.0.2
-
-$ curl http://http-ip.service.consul
-hello from 172.19.0.2
-

Here, we have an IP address that corresponds to each HTTP service that we have registered so we can clearly see that we are load balanced between our two containers.

-

 

-

Failure detection

-

We'll now add a Health Check to our service in order to ensure that it can be use safely by our users.

-

In this case we'll start to return on our node 01 and suppress the container named ekofr/http-ip  in order to recreate it with a Health Check:

-
$ eval $(docker-machine env swarm-node-01)
-
$ docker kill \
-    $(docker ps -q --filter='ancestor=ekofr/http-ip')
-

Registrator brings us some environment variables in order to add some Health Check for our containers into Consul and you can see the full list here: http://gliderlabs.com/registrator/latest/user/backends/#consul.

-

 

-

Idea here is to verify that port 80 is opened and application answers correctly so we'll add a script that simply executes a curl command:

-
$ docker run -d \
-    --net network-node-01 -p 80:8080 \
-    -e SERVICE_CHECK_SCRIPT="curl -s -f http://$(docker-machine ip swarm-node-01)" \
-    -e SERVICE_CHECK_INTERVAL=5s \
-    -e SERVICE_CHECK_TIMEOUT=1s \
-    ekofr/http-ip
-

 

-

You can do the same thing on your node 02 (by paying attention to modify the node-01  values to node-02 ) and you should now visualize these checks on the Consul web UI:

-

-

You can also use the Consul API in order to verify the good health of your services:

-
$ curl http://$(docker-machine ip consul):8500/v1/health/checks/http-ip ..
-[
-  {
-    "Status": "passing",
-    "Output": "hello from 172.18.0.2",
-    "ServiceName": "http-ip",
-  },
-  ...
-]
-

 

-

Conclusion

-

You are now able to install Consul on your projects architectures in order to ensure that services you contact are available and also be able to identify eventual issues that can occur on your services.

-

It's important to add a maximum of checks on elements that can make your services become unavailable (ensure that this one can be contacted and answer, ensure that remaining available disk space is sufficient, etc...).

-

Consul is a tool that integrates well in your architectures by its simplicity of use and its powerful API.

-{% endraw %} diff --git a/_posts/2016-07-19-behat-structure-functional-tests.md b/_posts/2016-07-19-behat-structure-functional-tests.md new file mode 100644 index 000000000..f71f5ced5 --- /dev/null +++ b/_posts/2016-07-19-behat-structure-functional-tests.md @@ -0,0 +1,356 @@ +--- +layout: post +title: 'Behat: structure your functional tests' +permalink: /en/behat-structure-functional-tests/ +author: vcomposieux +date: '2016-07-19 14:15:31 +0200' +date_gmt: '2016-07-19 12:15:31 +0200' +categories: + - Symfony + - Php +tags: + - symfony + - php + - behat +--- +In order to ensure that your application is running well, it's important to write functional tests. + +Behat is the most used tool with Symfony to handle your functional tests and that's great because it's really a complete suite. + +You should nevertheless know how to use it wisely in order to cover useful and complete test cases and that's the goal of this blog post. + +# Introduction + +## Functional testing: what's that? +When we are talking about functional testing we often mean that we want to automatize human-testing scenarios over the application. + +However, it is important to write the following test types to cover the functional scope: +* `Interface tests`: Goal here is to realize interface verifications to ensure that our web application features can be used over a browser, +* `Integration tests`: Goal of these tests is to ensure that sour code (already unit-tested) which makes the application running is acting as it should when all components are linked together. + +Idea is to develop and run both integration tests and interface tests with Behat. +Before we can go, please note that we will use a `Selenium` server which will receive orders by `Mink` (a Behat extension) and will pilot our browser (Chrome in our configuration). + +To be clear on the architecture we will use, here is a scheme that will resume the role of all elements: +Behat architecture schema + +## Behat set up +First step is to install Behat and its extensions as dependencies in our `composer.json` file: + +```json +"require-dev": { + "behat/behat": "~3.1", + "behat/symfony2-extension": "~2.1", + "behat/mink": "~1.7", + "behat/mink-extension": "~2.2", + "behat/mink-selenium2-driver": "~1.3", + "emuse/behat-html-formatter": "dev-master" +} +``` + +In order to make your future contexts autoloaded, you also have to add this little `PSR-4` section: + +```json +"autoload-dev": { + "psr-4": { + "Acme\Tests\Behat\Context\": "features/context/" + } +} +``` + +Now, let's create our **behat.yml** file in our project root directory in order to define our tests execution. + +Here is the configuration file we will start with: + +```yaml +default: + suites: ~ + extensions: + Behat\Symfony2Extension: ~ + Behat\MinkExtension: + base_url: "http://acme.tld/" + selenium2: + browser: chrome + wd_host: 'http://selenium-host:4444/wd/hub' + default_session: selenium2 + emuse\BehatHTMLFormatter\BehatHTMLFormatterExtension: + name: html + renderer: Twig,Behat2 + file_name: index + print_args: true + print_outp: true + loop_break: true + formatters: + pretty: ~ + html: + output_path: %paths.base%/web/reports/behat +``` + +We will talk of all of these sections in their defined order so let's start with the **suites** section which is empty at this time but we will implement it later when we will have some contexts to add into it. + +Then, we load some Behat extensions: + +* `Behat\Symfony2Extension` will allow us to inject Symfony services into our contexts (useful for integrations tests mostly), +* `Behat\MinkExtension` will allow us to pilot Selenium (drive itself the Chrome browser) so we fill in all the necessary information like the hostname, the Selenium server port number and the base URL we will use for testing, +* `emuse\BehatHTMLFormatter\BehatHTMLFormatterExtension` will generate a HTML report during tests execution (which is great to show to our customer for instance). + +Finally, in the `formatters` section we keep the `pretty` formatter in order to keep an output in our terminal and the HTML reports will be generated at the same time in the `web/reports/behat` directory in order to make them available over HTTP (it should not be a problem as you should not execute functional tests in production, be careful to restrict access in this case). +Now that Behat is ready and configured we will prepare our functional tests that we will split into two distinct Behat suites: `integration` and `interface`. + +# Writing functional tests (features) +In our example, we will write tests in order to ensure that a new user can register over a registration page. +We will have to start by writing our tests scenarios (in a `.feature` file) that we will put into a `features/` directory located at the project root directory. + +So for instance, we will have the following scenario: + +File: `features/registration/register.feature`: + +{% raw %} +``` +Feature: Register + In order to create an account + As a user + I want to be able to register on the application + +Scenario: I register when I fill my username and password only + Given I am on the registration page + And I register with username "johndoe" and password "azerty123" + When I submit the form + Then I should see the registration confirmation +``` +{% endraw %} + +# Integration tests + +As said previously, these tests are here to ensure all code written for the registration page can be executed and linked without any errors. + +To do so, we will create a new integration context that concerns the registration part under directory `features/context/registration`: + +File: `features/context/registration/IntegrationRegisterContext`: + +{% raw %} +```php +registerer = $registerer; + } + + /** + * @Given I am on the registration page + */ + public function iAmOnTheRegistrationPage() + { + $this->user = new User(); + } + + /** + * @Given /I register with username "(?P[^"]*)" and password "(?P[^"]*)"/ + */ + public function iRegisterWithUsernameAndPassword($username, $password) + { + $this->user->setUsername($username); + $this->user->setPassword($password); + } + + /** + * @When I submit the form + */ + public function iSubmitTheForm() + { + $this->response = $this->registerer->register($this->user); + } + + /** + * @Then I should see the registration confirmation message + */ + public function iShouldSeeTheRegistrationConfirmation() + { + if (!$this->response) { + throw new \RuntimeException('User is not registered.'); + } + } +} +``` +{% endraw %} + +Integration test for this part is now done for our feature. Let's write the interface test now! + +# Interface tests + +This test will be based on the same feature file without modifying the original written scenarios we wrote at the beginning. +That's why it is important to write a generic test that can be implemented both in an integration test and in an interface test. +So let's create that context that will be used for interface test (prefixed by Mink in our case, but you can prefix it by anything you want) under the directory `features/context/registration`. + +File: `features/context/registration/MinkRegisterContext`: + +{% raw %} +```php +visit('/register'); + } + + /** + * @Given /I register with username "(?P[^"]*)" and password "(?P[^"]*)"/ + */ + public function iRegisterWithUsernameAndPassword($username, $password) + { + $this->fillField('registration[username]', $username); + $this->fillField('registration[password]', $password); + } + + /** + * @When I submit the form + */ + public function iSubmitTheForm() + { + $this->pressButton('Register'); + } + + /** + * @Then I should see the registration confirmation message + */ + public function iShouldSeeTheRegistrationConfirmation() + { + $this->assertPageContainsText('Congratulations, you are now registered!'); + } +} +``` +{% endraw %} + +We just implemented an interface test based on the same scenario that the one we used for integration test so this class has exactly the same four methods with the same Behat annotations that we have implemented in our integration test class. +The only difference here is that in this context we ask Mink to ask to Selenium to do actions on the interface of our application by executing a browser instead of testing the code itself. + +# Context definition + +One more thing now, we have to add previously created contexts in our `suites` section in the `behat.yml` configuration file. + +{% raw %} +```yaml +suites: + integration: + paths: + - %paths.base%/features/registration + contexts: + - Acme\Tests\Behat\Context\Registration\IntegrationRegisterContext: + - "@acme.registration.registerer" + interface: + paths: + - %paths.base%/features/registration + contexts: + - Behat\MinkExtension\Context\MinkContext: [] + - Acme\Tests\Behat\Context\Registration\MinkRegisterContext: [] +``` +{% endraw %} + +It is important to see here that we can clearly split these kind of tests into two distinct parts `integration` and `interface`: each one will be executed with its own contexts. + +Also, as we have loaded the Symfony2 extension during the Behat set up, we have the possibility to inject Symfony services in our contexts and that case occurs here with the `acme.registration.registerer` service. + +# Tests execution + +In order to run all tests, simply execute in the project root directory: `bin/behat -c behat.yml`. +If you want to run the integration tests only: `bin/behat -c behat.yml --suite=integration`. +HTML report will be generated under the `web/reports/behat/` as specified in the configuration that will allow you to have a quick overview of failed tests which is cool when you have a lot of tests. + +# Link multiple contexts together + +At last, sometime you could need information from another context. For instance, imagine that you have a second step just after the register step. You will have to create two new `IntegrationProfileContext` and `MinkProfileContext` contexts. + +We will only talk about integration context in the following to simplify understanding. + +In this new step `IntegrationProfileContext`, you need some information obtained in the first step `IntegrationRegisterContext`. +This can be achieved thanks to the `@BeforeScenario` Behat annotation. +File: `features/context/registration/IntegrationProfileContext`: + +{% raw %} +```php +getEnvironment(); + + $this->registerContext = $environment->getContext( + 'Acme\Tests\Behat\Context\Registration\IntegrationRegisterContext' + ); + } +} +``` +{% endraw %} + +You now have an accessible property **$registerContext** and can access informations from this context. + +# Conclusion + +Everything starts from well-written tests which have to be thoughtful in order to allow a technical implementation on both integration tests and interface tests. + +The choosen structure about classifying its tests is also important in order to quickly find tests when the application grows. diff --git a/_posts/2016-07-19-behat-structurez-vos-tests-fonctionnels.md b/_posts/2016-07-19-behat-structurez-vos-tests-fonctionnels.md new file mode 100644 index 000000000..21e73078f --- /dev/null +++ b/_posts/2016-07-19-behat-structurez-vos-tests-fonctionnels.md @@ -0,0 +1,370 @@ +--- +layout: post +title: 'Behat : structurez vos tests fonctionnels' +permalink: /fr/behat-structurez-vos-tests-fonctionnels/ +author: vcomposieux +date: '2016-07-19 10:16:11 +0200' +date_gmt: '2016-07-19 08:16:11 +0200' +categories: + - Symfony + - Php +tags: + - symfony + - php + - behat +--- +Il est important de mettre en place des tests fonctionnels sur les projets afin de s'assurer du bon fonctionnement de l'application. + +Lorsqu'il s'agit d'une application Symfony, Behat est l'outil le plus souvent utilisé pour réaliser ces tests et c'est tant mieux car cet outil est très complet. + +Il faut néanmoins savoir l'utiliser à bon escient afin de couvrir des cas de tests utiles et complets, c'est ce que nous allons voir dans cet article. + +# Introduction + +## Tests fonctionnels : qu'est-ce ? + +Lorsque nous parlons de "tests fonctionnels", nous entendons bien souvent vouloir tester l'interface de l'application (site web), autrement dit, automatiser des tests qui pourraient être faits par un humain. + +Or, il est important d'écrire les cas de tests suivants afin de couvrir le périmètre fonctionnel : +* `tests d'interface` : il s'agit de réaliser des contrôles d'interface pour s'assurer que le comportement de l'application web réagit correctement, +* Les `tests d'intégration` : il s'agit de s'assurer que le code (testé unitairement) qui fait tourner l'application réagit bien comme il le devrait lorsque tous les éléments sont assemblés. + +Il conviendra alors de lancer à la fois les tests d'intégration et les tests d'interface avec Behat. + +Avant de commencer, notez que dans cet exemple, nous allons utiliser un serveur `Selenium` qui recevra les informations fournies par `Mink` (extension de Behat) et qui pilotera ensuite notre navigateur (Chrome, dans notre configuration).n +Pour être clair sur l'architecture, voici un schéma qui résume le rôle de chacun : + +Schéma d'architecture Behat/Selenium + +# Mise en place de Behat + +La première étape est d'installer Behat et ses extensions en tant que dépendance dans notre fichier `composer.json` : + +{% raw %} +```json +"require-dev": { + "behat/behat": "~3.1", + "behat/symfony2-extension": "~2.1", + "behat/mink": "~1.7", + "behat/mink-extension": "~2.2", + "behat/mink-selenium2-driver": "~1.3", + "emuse/behat-html-formatter": "dev-master" +} +``` +{% endraw %} + +Afin que vos futurs contextes soient autoloadés, nous allons également ajouter la section `PSR-4` suivante : + +{% raw %} +```json +"autoload-dev": { + "psr-4": { + "Acme\Tests\Behat\Context\": "features/context/" + } +} +``` +{% endraw %} + +Maintenant, créons le fichier de configuration `behat.yml` à la racine de notre projet afin d'architecturer nos tests. + +Voici le fichier de configuration à partir duquel nous allons débuter : + +{% raw %} +```json +
n{% raw %}ndefault:
+    suites: ~
+    extensions:
+        Behat\Symfony2Extension: ~
+        Behat\MinkExtension:
+            base_url: "http://acme.tld/"
+            selenium2:
+                browser: chrome
+                wd_host: 'http://selenium-host:4444/wd/hub'
+            default_session: selenium2
+        emuse\BehatHTMLFormatter\BehatHTMLFormatterExtension:
+            name: html
+            renderer: Twig,Behat2
+            file_name: index
+            print_args: true
+            print_outp: true
+            loop_break: true
+    formatters:
+        pretty: ~
+        html:
+            output_path: %paths.base%/web/reports/behat
+```
+{% endraw %}
+
+Si nous prenons les sections dans leur ordre, nous avons avant tout une section `suites` pour le moment vide mais que nous allons alimenter par la suite de cet article.
+
+Ensuite, nous chargeons ici plusieurs extensions de Behat :
+
+* L'extension `Behat\Symfony2Extension` permettant notamment d'injecter des services Symfony dans nos classes contextes de test,
+* L'extension `Behat\MinkExtension` qui va nous permettre de piloter notre Selenium (qui pilotera lui-même notre navigateur Chrome), nous lui fournissons donc les informations nécessaires tels que le host et port du serveur Selenium ainsi que la base de l'URL à contacter,
+* L'extension `emuse\BehatHTMLFormatter\BehatHTMLFormatterExtension` qui nous permettra de générer un rapport HTML lors du lancement des tests (toujours sympa à présenter au client).
+
+Notons enfin que dans la section **formatters**, nous conservons le formatter **pretty** afin d'avoir une sortie sympa sur notre terminal et que les rapports HTML seront quant à eux générés dans le répertoire `web/reports/behat` afin qu'ils soient accessibles en HTTP (à priori pas de soucis car vous ne devriez pas jouer ces tests en production, attention à la restriction d'accès si c'est le cas).
+
+Maintenant que Behat est prêt et configuré, nous allons préparer nos tests fonctionnels que nous allons découper en deux "suites" Behat distinctes : `integration` et `interface`.
+
+# Ecriture des tests fonctionnels (features)
+
+Nous allons partir sur des tests permettant de s'assurer du bon fonctionnement d'une page d'inscription.
+
+Nous devons avant tout écrire nos scénarios de tests fonctionnels (fichier **.feature**) que nous allons placer dans un répertoire **features/** à la racine du projet.
+
+Nous allons donc avoir, par exemple, le scénario suivant :
+
+Fichier : `features/registration/register.feature` :
+{% raw %}
+```
+Feature: Register
+    In order to create an account
+    As a user
+    I want to be able to register on the application
+
+Scenario: I register when I fill my username and password only
+    Given I am on the registration page
+        And I register with username "johndoe" and password "azerty123"
+    When I submit the form
+    Then I should see the registration confirmation
+```
+{% endraw %}
+
+## Tests d'intégration
+
+Il va maintenant convenir d'implémenter le code qui va nous permettre de tester que le code écrit pour l'inscription d'un utilisateur peut être exécuté et enchaîné sans erreur.
+
+Nous allons donc créer un contexte d'intégration propre à l'inscription sous le répertoire `features/context/registration` :
+
+Fichier : `features/context/registration/IntegrationRegisterContext` :
+
+{% raw %}
+```php
+registerer = $registerer;
+    }
+
+    /**
+     * @Given I am on the registration page
+     */
+    public function iAmOnTheRegistrationPage()
+    {
+        $this->user = new User();
+    }
+
+    /**
+     * @Given /I register with username "(?P[^"]*)" and password "(?P[^"]*)"/
+     */
+    public function iRegisterWithUsernameAndPassword($username, $password)
+    {
+        $this->user->setUsername($username);
+        $this->user->setPassword($password);
+    }
+
+    /**
+     * @When I submit the form
+     */
+    public function iSubmitTheForm()
+    {
+        $this->response = $this->registerer->register($this->user);
+    }
+
+    /**
+     * @Then I should see the registration confirmation message
+     */
+    public function iShouldSeeTheRegistrationConfirmation()
+    {
+        if (!$this->response) {
+            throw new \RuntimeException('User is not registered.');
+        }
+    }
+}
+```
+{% endraw %}
+
+L'implémentation du test d'intégration est terminé pour cette feature !
+Passons maintenant au test d'interface !
+
+## Tests d'interface
+
+Ce test va se baser sur la même feature et nous n'avons absolument rien modifié dans le test précédemment écrit. C'est pourquoi il est important de bien rédiger ses tests fonctionnels afin qu'ils restent assez génériques pour être implémentés à la fois en test d'intégration et en test d'interface.
+
+Créons donc le contexte qui sera utilisé pour le test d'interface (préfixé par Mink dans notre cas, mais vous pouvez préfixer par ce que vous voulez) sous le même répertoire `features/context/registration` :
+
+Fichier : `features/context/registration/MinkRegisterContext` :
+
+{% raw %}
+```php
+visit('/register');
+    }
+
+    /**
+     * @Given /I register with username "(?P[^"]*)" and password "(?P[^"]*)"/
+     */
+    public function iRegisterWithUsernameAndPassword($username, $password)
+    {
+        $this->fillField('registration[username]', $username);
+        $this->fillField('registration[password]', $password);
+    }
+
+    /**
+     * @When I submit the form
+     */
+    public function iSubmitTheForm()
+    {
+        $this->pressButton('Register');
+    }
+
+    /**
+     * @Then I should see the registration confirmation message
+     */
+    public function iShouldSeeTheRegistrationConfirmation()
+    {
+        $this->assertPageContainsText('Congratulations, you are now registered!');
+    }
+}
+```
+{% endraw %}
+
+Nous venons d'implémenter un test d'interface basé sur le même scénario que celui que nous avons utilisé pour notre test d'intégration, reprenant exactement les quatre méthodes implémentées précédemment avec les mêmes annotations Behat.
+
+La seule différence est que dans ce contexte, Mink va demander à Selenium d'effectuer les actions au niveau de l'interface de notre application en pilotant un navigateur au lieu de tester le code lui-même.
+
+# Définitions des contextes
+
+Il ne nous reste plus qu'à ajouter les contextes créés précédemment sous notre section **suites** dans le fichier de configuration `behat.yml` :
+
+{% raw %}
+```yaml
+    suites:
+        integration:
+            paths:
+                - %paths.base%/features/registration
+            contexts:
+                - Acme\Tests\Behat\Context\Registration\IntegrationRegisterContext:
+                    - "@acme.registration.registerer"
+        interface:
+            paths:
+                - %paths.base%/features/registration
+            contexts:
+                - Behat\MinkExtension\Context\MinkContext: []
+                - Acme\Tests\Behat\Context\Registration\MinkRegisterContext: []
+```
+{% endraw %}
+
+Il est important de voir ici que nous découpons clairement les tests en deux suites distinctes : `integration` et `interface` : chacune d'entre elles sera exécutée avec les contextes qui lui sont propres.
+
+Etant donné que nous avons chargés l'extension Symfony2 lors de la mise en place de Behat, nous avons la possibilité d'injecter des services Symfony dans nos contextes, c'est le cas ici avec le service `acme.registration.registerer`.
+
+# Exécution des tests
+
+Pour lancer tous les tests, exécutez simplement, à la racine du projet : `bin/behat -c behat.yml`.
+Pour lancer uniquement la suite d'integration, par exemple : `bin/behat -c behat.yml --suite=integration`.
+
+Le rapport HTML est quand à lui généré dans `web/reports/behat/`, comme spécifié dans notre configuration, ce qui vous permettra d'avoir un aperçu rapide des tests qui échouent, plutôt pratique lorsque vous avez de nombreux tests.
+
+# Lier plusieurs contextes entre eux**
+
+Pour terminer, vous pourrez parfois avoir besoin de lier les contextes entre eux. Par exemple, imaginons que vous ayez une deuxième page sur votre formulaire d'inscription pour renseigner les informations personnelles, vous allez alors créer deux nouveaux contextes **IntegrationProfileContext** et `MinkProfileContext`.
+
+Partons sur le contexte d'intégration pour simplifier l'explication, l'idée est de ne pas dupliquer le code précédemment créé et permettant de tester la première étape `IntegrationRegisterContext` et de réutiliser ces informations dans le nouveau contexte `IntegrationProfileContext`.
+
+Ceci est possible grâce à l'annotation `@BeforeScenario` de Behat.
+
+Fichier : `features/context/registration/IntegrationProfileContext` :
+
+{% raw %}
+```php
+getEnvironment();
+
+        $this->registerContext = $environment->getContext(
+            'Acme\Tests\Behat\Context\Registration\IntegrationRegisterContext'
+        );
+    }
+}
+```
+{% endraw %}
+
+Vous avez maintenant à disposition une propriété `$registerContext` et pouvez accéder à des informations qui proviennent du contexte précédent.
+
+# Conclusion
+
+Tout part de l'écriture des tests fonctionnels qui doivent être bien réfléchis pour ensuite permettre une implémentation technique à la fois en test d'intégration mais aussi en test d'interface.
+
+La structure choisie pour classer ses tests fonctionnels est aussi importante pour pouvoir s'y retrouver rapidement dans les différents scénarios de test lorsque l'application prend de l'ampleur.
diff --git a/_posts/2016-09-27-utiliser-le-composant-workflow-de-symfony.md b/_posts/2016-09-27-utiliser-le-composant-workflow-de-symfony.md
new file mode 100644
index 000000000..c5f946288
--- /dev/null
+++ b/_posts/2016-09-27-utiliser-le-composant-workflow-de-symfony.md
@@ -0,0 +1,193 @@
+---
+layout: post
+title: Utiliser le composant Workflow de Symfony
+permalink: /fr/utiliser-le-composant-workflow-de-symfony/
+author: vcomposieux
+date: '2016-09-27 11:05:28 +0200'
+date_gmt: '2016-09-27 09:05:28 +0200'
+categories:
+    - Symfony
+    - Php
+tags:
+    - symfony
+    - php
+    - workflow
+---
+Depuis Symfony 3.2, un nouveau composant très utile a vu le jour : [le composant Workflow](http://symfony.com/blog/new-in-symfony-3-2-workflow-component).
+Celui-ci est en effet très pratique et peut très largement simplifier vos développements lorsque vous avez, par exemple, à gérer des workflows de statut dans votre application.
+
+# Installation
+
+Dans tous les cas, vous devez installer la dépendance suivante :
+
+```json
+"symfony/workflow": "~3.2@dev"
+```
+
+Si vous utilisez une version antérieure de Symfony mais >=2.3, c'est aussi possible mais il vous faudra également installer ce bundle non-officiel qui embarque le composant et ajoute la configuration nécessaire sous le namespace du bundle :
+
+```json
+"fduch/workflow-bundle": "~0.2@dev"
+```
+
+Pensez bien à activer le bundle dans votre kernel.
+
+# Configuration
+
+Il va maintenant nous falloir définir la configuration de notre workflow et ainsi définir les statuts (appelés places) et transitions possibles.
+Pour cet article, nous sommes partis sur un exemple basé sur les statuts d'une pull request. Celle-ci peut avoir les états suivants : `opened` , `closed` , `needs_review` , `reviewed`  et enfin `merged`.
+
+Cependant, elle ne pourra, par exemple, pas être passée en `merged`  sans être passée par le statut `reviewed` . C'est ici que le composant Workflow prend tout son sens.
+
+Voici ce que donne notre configuration complète :
+```yaml
+workflow:
+    workflows:
+        pull_request:
+            marking_store:
+                type: multiple_state
+                arguments:
+                    - state
+            supports:
+                - AppBundle\Entity\PullRequest
+            places:
+                - opened
+                - closed
+                - needs_review
+                - reviewed
+                - merged
+            transitions:
+                feedback:
+                    from: opened
+                    to:   needs_review
+                review:
+                    from: [opened, needs_review]
+                    to:   reviewed
+                merge:
+                    from: reviewed
+                    to:   merged
+                close:
+                    from: [opened, needs_review, reviewed]
+                    to:   closed
+```
+
+Nous spécifions ici que nous souhaitons utiliser un workflow de type `multiple_state` . Notez que si vous souhaitez utiliser une transition simple d'un statut vers un autre, vous pouvez utiliser ici `single_state`.
+Nous disposons donc également d'une classe `AppBundle\Entity\PullRequest`  qui dispose d'une propriété `state`  ainsi que son setter et getter associé (le composant va utiliser les méthodes getter et setter pour changer l'état et/ou obtenir l'état courant) :
+
+```php
+state = $state;
+    }
+
+    public function getState()
+    {
+        return $this->state;
+    }
+}
+```
+
+Nous avons terminé, nous pouvons maintenant commencer à utiliser le composant Workflow !
+
+# Utilisation
+
+La première chose utile à effectuer après avoir écrit votre workflow est de générer une représentation graphique de celui-ci (sous un format [Graphviz](http://www.graphviz.org)).
+
+Pour ce faire, nous utilisons la commande Symfony :
+
+```bash
+$ bin/console workflow:dump pull_request
+```
+
+Celle-ci va vous générer un code Graphviz qui donne le schéma suivant :
+
+Workflow - Graphviz
+
+Celui-ci permet vraiment de donner une vision claire de son workflow, à tous les niveaux (développeurs, product owners, clients, ...).
+Le composant Workflow implémente des méthodes permettant d'effectuer une transition, vérifier si une transition peut être effectuée avec l'état actuel et lister les transitions possibles avec l'état actuel.
+
+Pour vérifier si vous pouvez effectuer une transition et l'appliquer, rien de plus simple :
+
+```php
+getPullRequestManager()->find($identifier);
+
+        // Nous obtenons le service "workflow."
+        $workflow = $this->get('workflow.pull_request');
+
+        if ($workflow->can($pullRequest, 'merge')) {
+            $workflow->apply($pullRequest, 'merge');
+        }
+
+        ...
+    }
+}
+```
+
+Si vous ne passez pas par la méthode `can()` , la méthode `apply()`  renverra une exception si la transition ne peut pas être effectuée. Vous pouvez donc également catcher cette exception de type `Symfony\Component\Workflow\Exception\LogicException`.
+
+Pour lister les transitions disponibles :
+
+```php
+$workflow->getEnabledTransitions($pullRequest);
+```
+
+Globalement, l'utilisation du composant se limite à ces 3 méthodes. Comme vous le remarquez, il devient très simple d'utiliser un workflow, même complexe !
+
+# Branchez-vous sur les événements !
+
+Le composant utilise également plusieurs événements, à savoir, dans l'ordre chronologique :
+
+* `workflow.leave`  : lorsque notre pull request va se voir dépourvue de son dernier statut,
+* `workflow.transition`  : lorsque la transition vers le nouvel état est lancée,
+* `workflow.enter`  : lorsque le nouvel état est défini sur notre pull request,
+* `workflow.guard`  : pour vous éviter de rendre la transition possible, vous pouvez utiliser cet événement pour définir votre événement bloqué : `$event->setBlocked(true);`
+
+Enfin, sachez que ces événements existent aussi en version unique pour chaque workflow afin de vous permettre de vous brancher dessus uniquement sur certains workflows. Il vous faut alors utiliser le nom `workflow.pull_request.enter`.
+
+Faisons encore mieux, vous pouvez même vous brancher sur une transition particulière :
+
+* `workflow.pull_request.enter.needs_review`  : permet de se brancher uniquement lorsque nous définissons un nouvel état `needs_review`  à notre pull request, nous pourrons alors envoyer un email à l'auteur pour qu'il corrige certaines choses,
+* `workflow.pull_request.transition.merge`  : interviendra lorsque la transition de merge prendra effet sur notre pull request.
+
+# Conclusion
+
+Le composant Workflow est vraiment très utile dans la gestion d'états ou de statuts sur la plupart des projets.
+
+N'hésitez pas à l'utiliser, sa facilité de configuration et d'utilisation vous aidera grandement sur vos projets.
+Aussi, il m'a permis de donner un graphique clair sur un workflow complexe à toutes les personnes avec qui je travaillais.
diff --git a/_posts/2016-09-29-symfony-workflow-component.md b/_posts/2016-09-29-symfony-workflow-component.md
new file mode 100644
index 000000000..f73cf23ae
--- /dev/null
+++ b/_posts/2016-09-29-symfony-workflow-component.md
@@ -0,0 +1,194 @@
+---
+layout: post
+title: Use the Symfony Workflow component
+permalink: /en/symfony-workflow-component/
+author: vcomposieux
+date: '2016-09-29 10:04:20 +0200'
+date_gmt: '2016-09-29 08:04:20 +0200'
+categories:
+    - Symfony
+    - Php
+tags:
+    - symfony
+    - php
+    - workflow
+---
+Since Symfony 3.2, a new useful component was born: the [Workflow component](http://symfony.com/blog/new-in-symfony-3-2-workflow-component).
+It is indeed really convenient and can simplify greatly your developments when you have to manage status workflows in your application, that occurs a lot.
+
+# Installation
+
+In all cases, you have to install the following dependency:
+
+```json
+"symfony/workflow": "~3.2@dev"
+```
+
+If you use an earlier version of Symfony but >=2.3 you are also able to use this component, but you have to install the following non-official bundle, which loads the component itself and add the required configuration under the bundle's namespace:
+
+```json
+"fduch/workflow-bundle": "~0.2@dev"
+```
+
+Do not forget to enable the bundle in your kernel class.
+
+# Configuration
+
+Time has come to write our workflow configuration. We will have to define all our places (statuses / states) and available transitions.
+In this blog post, we will take a pull request status example. A pull request can have one of the following status: `opened` , `closed` , `needs_review` , `reviewed`  or `merged`.
+However, it cannot be, for instance, moved from the `merged` status without having the `reviewed`  status before. The workflow component makes sense here.
+
+Here is our full workflow configuration:
+```yaml
+workflow:
+    workflows:
+        pull_request:
+            marking_store:
+                type: multiple_state
+                arguments:
+                    - state
+            supports:
+                - AppBundle\Entity\PullRequest
+            places:
+                - opened
+                - closed
+                - needs_review
+                - reviewed
+                - merged
+            transitions:
+                feedback:
+                    from: opened
+                    to:   needs_review
+                review:
+                    from: [opened, needs_review]
+                    to:   reviewed
+                merge:
+                    from: reviewed
+                    to:   merged
+                close:
+                    from: [opened, needs_review, reviewed]
+                    to:   closed
+```
+
+Here, we specify we want to use a `multiple_state`  workflow. Please not that if you want to use a simple transition from one state to another, you can use a `single_state`.
+For this example, we also have defined a `AppBundle\Entity\PullRequest` class which has a `state`  property and associated setter and getter methods (component will use these methods to manage transitions):
+
+```php
+state = $state;
+    }
+
+    public function getState()
+    {
+        return $this->state;
+    }
+}
+```
+
+
+Everything is now ready, we can start to use the Workflow component!
+
+# Usage
+
+First useful thing to do after you have written your workflow configuration is to generate a graph using the Symfony command. The command will generate one graph using the [Graphviz](http://www.graphviz.org) format.
+
+Here is the Symfony command you have to run:
+
+```bash
+$ bin/console workflow:dump pull_request
+```
+
+The generated Graphviz will give you the following diagram:
+
+Workflow - Graphviz
+
+This one gives you a really clear vision of your workflow and allows everyone at every level (developers, product owners, customers, ...) to understand the business logic.
+The Workflow component implements methods that allow you to verify if a transition is applicable and to later apply it depending on the current status and to also list all enabled transitions.
+
+In order to check if you can apply a specific transition and apply it, simply use the following code:
+
+```php
+getPullRequestManager()->find($identifier);
+
+        // Nous obtenons le service "workflow."
+        $workflow = $this->get('workflow.pull_request');
+
+        if ($workflow->can($pullRequest, 'merge')) {
+            $workflow->apply($pullRequest, 'merge');
+        }
+
+        ...
+    }
+}
+```
+
+In the case you do not want to use the `can()` method, the `apply()` ``` one``` will throw an exception if the transition cannot be effectively done, so you will be able to catch exceptions on the `Symfony\Component\Workflow\Exception\LogicException` type.
+
+
+To list all enabled transitions:
+
+```php
+$workflow->getEnabledTransitions($pullRequest);
+```
+
+Overall, the component usage is just as simple as these 3 methods. As you can see, complex workflows are now easier to manage!
+
+# Tune in for events!
+
+The component also dispatches multiple events, chronologically sorted as:
+
+* `workflow.leave`: when our pull request will leave its current state,
+* `workflow.transition`: when the transition to the new state is launched,
+* `workflow.enter`: when the new state is just defined on our pull request,
+* `workflow.guard`: this one allows you to prevent the asked transition from occurring, you can do that by calling the following method: `$event->setBlocked(true);`
+
+Finally, you have to know that these events also exist in a unique way for each workflow in order to allow you to tune in your workflow events only.
+If you want to do that, you have to listen to the following name: `workflow.pull_request.enter`.
+
+Let's do better than that: you are also able to listen to a specific transition or a state for a specific workflow:
+
+* `workflow.pull_request.enter.needs_review`: is only dispatched when a `needs_review` state is defined on our pull request. We could for instance send an email to the author with the changes the reviewer has suggested,
+* `workflow.pull_request.transition.merge`: will occur when the merge transition will be dispatched on our pull request.
+
+# Conclusion
+
+The Workflow component is a really useful component to manage state or status on most of web applications.
+Do not hesitate to use it because its simplicity of configuration and use will probably help you a lot on your projects.
+Also, this component helps me a lot to give people I was working with a clear vision on a complex workflow we have to manage. The graph generation allows to clarify all of that for everyone!
diff --git a/_posts/2016-12-01-creer-votre-premier-package-pour-atom.md b/_posts/2016-12-01-creer-votre-premier-package-pour-atom.md
new file mode 100644
index 000000000..e80e69b88
--- /dev/null
+++ b/_posts/2016-12-01-creer-votre-premier-package-pour-atom.md
@@ -0,0 +1,269 @@
+---
+layout: post
+title: Créer votre premier package pour Atom
+permalink: /fr/creer-votre-premier-package-pour-atom/
+author: vcomposieux
+date: '2016-12-01 12:14:17 +0100'
+date_gmt: '2016-12-01 11:14:17 +0100'
+categories:
+    - Javascript
+tags:
+    - atom
+    - package
+    - babel
+    - jasmine
+---
+
+# Introduction à Atom
+
+[Atom](https://atom.io) est un éditeur de texte (principalement utilisé pour du code) multi-plateforme développé par la société GitHub et qui s'appuie sur un autre framework développé par GitHub : Electron, qui permet de développer des applications natives pour chaque système d'exploitation à partir de code Javascript.
+
+Le grand intérêt d'Atom est qu'il peut être étendu très facilement avec un peu de code Javascript et c'est ce que nous allons voir dans cet article. Ainsi, tout le monde peut écrire son "package" pour Atom.
+
+Aussi, sa communauté très active compte déjà un bon nombre de packages : `5 285` au moment où j'écris cet article.
+Vous pouvez les retrouver à l'URL suivante : [https://atom.io/packages](https://atom.io/packages)
+
+Si toutefois vous ne trouvez pas votre bonheur dans les packages déjà proposés, vous pouvez alors écrire le votre et nous allons voir qu'il n'y a rien de compliqué.
+
+# Générer son premier package
+
+Pour créer votre premier package, rassurez-vous, vous n'allez pas partir de rien. En effet, nous allons utiliser la commande fournie par le package `Package Generator` natif à Atom.
+Pour se faire, il vous suffira de naviguer dans : `Packages` -> `Package Generator` -> `Generate Atom Package`.
+
+Lors de la génération, vous pouvez choisir le langage que vous souhaitez utiliser pour développer votre package, entre `Javascript` et `Coffeescript`. Cet article est rédigé en Javascript.
+
+Atom vous ouvrira alors une nouvelle fenêtre à l'intérieur de votre nouveau package, nommé `my-package`.
+
+# Structure d'un package
+
+Nous allons maintenant détailler la structure par défaut du projet :
+
+```
+├── CHANGELOG.md
+├── LICENSE.md
+├── README.md
+├── keymaps
+│   └── my-package.json         <- Raccourcis clavier enregistrés par votre package
+├── lib
+│   ├── my-package-view.js
+│   └── my-package.js           <- Point d'entrée de votre package
+├── menus
+│   └── my-package.json         <- Déclaration des menus que votre package ajoute dans Atom
+├── package.json                <- Fichier descriptif et de dépendances de votre package
+├── spec                        <- Répertoire de tests (Jasmine) de votre package
+│   ├── my-package-spec.js
+│   └── my-package-view-spec.js
+└── styles                      <- Feuilles de styles utilisées par votre package
+└── my-package.less
+```
+
+Le premier élément à renseigner est le fichier `package.json`  qui doit contenir les informations relatives à votre package tel que son nom, sa version, license, mots clés pour trouver votre package et également ses librairies de dépendances.
+Notez également la présence dans ce fichier d'une section `activationCommands`  qui vous permet de définir la commande exécutée lors de l'activation de votre package.
+
+Nous avons ensuite le fichier `keymaps/my-package.json`  qui vous permet d'enregistrer des raccourcis clavier dans votre application, de façon très simple :
+
+```json
+{
+  "atom-workspace": {
+    "ctrl-alt-p": "my-package:toggle"
+  }
+}
+```
+
+Passons maintenant au point d'entrée de votre package. Il s'agit de ce qui se trouve dans `lib/my-package.js`.
+Dans ce fichier est exporté un objet par défaut qui contient une propriété `subscriptions`  et des méthodes `activate()`  et `deactivate()`  notamment.
+
+Lors de l'activation de notre package (dans la méthode `activate()` ), nous allons enregistrer dans notre propriété `subscriptions`  un objet de type [CompositeDisposable](https://atom.io/docs/api/latest/CompositeDisposable)  qui nous permettra d'ajouter et d'éventuellement plus tard, supprimer des commandes disponibles dans notre package :
+
+```js
+activate(state) {
+  this.subscriptions = new CompositeDisposable();
+  this.subscriptions.add(atom.commands.add('atom-workspace', {
+    'my-package:toggle': () => this.toggle()
+  }));
+}
+```
+
+Notre commande étant enregistrée, nous pouvons dès maintenant l'exécuter en ouvrant la palette de commande : `My Package: Toggle`.
+
+Celle-ci va exécuter le code contenu dans la méthode `toggle()`  de votre classe, soit dans le package par défaut, afficher une petite fenêtre en haut de l'écran.
+Vous pouvez ajouter autant de commandes que vous le souhaitez et surtout, découper votre code comme vous le sentez.
+
+## Ajouter des paramètres dans mon package
+
+Vous avez la possibilité d'ajouter des paramètres à votre package et ceci est rendu possible grâce au composant [Config](https://atom.io/docs/api/latest/Config).
+
+Il vous suffira d'ajouter une propriété `config`  à votre classe en définissant un objet avec la définition de chaque élément que vous souhaitez voir apparaître dans vos paramètres :
+
+```json
+config: {
+  "gitlabUrl": {
+    "description": "If you rely on a private Gitlab server, please type your base URI here (default: https://gitlab.com).",
+    "type": "string",
+    "default": "https://gitlab.com"
+  }
+}
+```
+
+La configuration offre un grand nombre de valeurs disponibles (`boolean` , `color` , `integer` , `string` , ...) ce qui permet de laisser un grand nombre de choix à vos utilisateurs.
+Les paramètres de votre package apparaîtront alors pour votre package, sous Atom :
+
+Atom - Settings
+
+Vous pourrez alors, à tout moment dans votre code, obtenir dans votre package la valeur définie par l'utilisateur (ou la valeur par défaut fournie si aucune valeur n'a été renseignée) via :
+
+```js
+let gitlabUrl = atom.config.get('gitlabUrl');
+```
+
+# Tour d'horizon des composants
+
+Vous pouvez maintenant commencer à développer votre package, nous allons donc parcourir les différents composants qui sont à votre disposition et que vous pourrez utiliser dans votre package !
+
+## TextEditor : Agissez sur l'éditeur de texte
+
+Avec le composant `TextEditor` , vous allez pouvoir insérer du texte dans votre éditeur, enregistrer le fichier, jouer sur l'historique des actions (aller en avant ou arrière), déplacer le curseur dans l'éditeur, copier/coller dans le presse-papier, jouer sur l'indentation, scroller, etc ...
+Quelques commandes en exemple, ici pour insérer du texte à une coordonnée donnée et enregistrer le fichier :
+
+```js
+editor.setCursorBufferPosition([row, column]);
+editor.insertText('foo');
+editor.save();
+```
+
+## ViewRegistry et View : Créez et affichez votre propre fenêtre
+
+Ces composants vont vous permettre de créer votre fenêtre à l'intérieur d'Atom et de l'afficher.
+Vous disposez d'un exemple d'utilisation du composant `View`  dans le package généré par défaut :
+
+```js
+export default class MyPackageView {
+    constructor(serializedState) {
+      // Create root element
+      this.element = document.createElement('div');
+      this.element.classList.add('my-package');
+
+      // Create message element
+      const message = document.createElement('div');
+      message.textContent = 'The MyPackage package is Alive! It\'s ALIVE!';
+      message.classList.add('message');
+      this.element.appendChild(message);
+    }
+
+    // ...
+}
+
+let myPackageView = new MyPackageView(state.myPackageViewState);
+let modalPanel = atom.workspace.addModalPanel({
+item: myPackageView.getElement(),
+visible: false;
+});
+
+modalPanel.show();
+```
+
+## NotificationManager et Notification : Informez vos utilisateurs via des notifications
+
+Vous avez également la possibilité de rendre des notifications dans l'éditeur de plusieurs niveaux, avec les commandes suivantes :
+
+```js
+atom.notifications.addSuccess('My success notification');
+atom.notifications.addInfo('My info notification');
+atom.notifications.addWarning('My warning notification');
+atom.notifications.addError('My error notification');
+atom.notifications.addFatalError('My fatal error notification');
+```
+
+## GitRepository
+
+Celui-ci est très intéressant : vous pouvez en effet accéder à toutes les propriétés du repository Git actuellement utilisé par l'utilisateur.
+Vous pourrez alors obtenir (entre autres) la branche actuellement utilisée, obtenir l'URL du remote, voir si un fichier est nouveau ou modifié ou encore accéder au diff.
+
+```js
+let repository = atom.project.getRepositoryForDirectory('/path/to/project');
+
+console.log(repository.getOriginURL());               // -> git@github.com:eko/atom-pull-request.git
+console.log(repository.getShortHead());               // -> master
+console.log(repository.isStatusNew('/path/to/file')); // -> true
+```
+
+## Encore bien d'autres choses à découvrir ...
+Je vous ai présenté les composants les plus courants mais je vous invite à visiter la documentation de l'API si vous souhaitez aller plus loin : [https://atom.io/docs/api/latest/AtomEnvironment](https://atom.io/docs/api/latest/AtomEnvironment)
+
+# Tester votre package
+
+Nous en arrivons au moment de tester notre package, et pour cela, Atom utilise [Jasmine](https://jasmine.github.io)
+
+Votre package vient déjà avec un fichier de test pré-défini :
+
+```js
+import MyPackageView from '../lib/my-package-view';
+
+describe('MyPackageView', () => {
+  it('has one valid test', () => {
+    expect('life').toBe('easy');
+  });
+});
+```
+
+
+Les tests Jasmine doivent être structurés de la façon suivante :
+
+* `describe()`  : Une suite de test commence par une fonction describe qui prend un nom en premier argument et une fonction en deuxième argument,
+* `it()`  : Une spécification est ajoutée par ce mot clé, il doit être contenu à l'intérieur d'une suite de test,
+* `expect()`  : Il s'agit d'une assertion, lorsqu'on s'attend à avoir un résultat donné.
+
+C'est maintenant à vous de jouer et de tester votre logique applicative.
+
+Vous pouvez lancer les specs via le menu d'Atom : `View`  -> `Packages`  -> `Run Package Specs`.
+
+# Publier votre package
+
+Notre package est maintenant prêt à être publié !
+
+Atom - Fusée
+
+Pour se faire, nous allons utiliser l'outil CLI installé avec Atom : `apm`.
+
+Après avoir pushé votre code sur un repository Github, rendez-vous dans le répertoire de votre package et jouez la commande suivante :
+
+```bash
+$ apm publish --tag v0.0.1 minor
+
+Preparing and tagging a new version ✓
+Pushing v0.0.1 tag ✓
+...
+```
+
+La commande va s'occuper de créer et pusher le tag de la version spécifiée et référencer cette version sur le registry d'Atom.
+Félicitations, votre package est maintenant publié et visible à l'URL : `https://atom.io/packages/` !
+
+# Intégration continue
+
+Afin de vous assurer que votre package fonctionne toujours sur la version stable d'Atom mais également pour anticiper et tester également la version bêta, vous pouvez mettre en place [Travis-CI](https://travis-ci.org) sur le repository de votre code avec le fichier suivant :
+
+```yaml
+language: objective-c
+
+notifications:
+  email:
+    on_success: never
+    on_failure: change
+
+script: 'curl -s https://raw.githubusercontent.com/nikhilkalige/docblockr/develop/spec/atom-build-package.sh | sh'
+
+env:
+  global:
+    - APM_TEST_PACKAGES=""
+
+  matrix:
+    - ATOM_CHANNEL=stable
+    - ATOM_CHANNEL=beta<
+```
+
+# Conclusion
+
+Je trouve personnellement qu'il s'agit d'une vraie révolution de pouvoir interagir à tel point avec l'éditeur de texte, l'outil utilisé la plupart du temps par les développeurs.
+L'API d'Atom est déjà très riche et est très simple à utiliser, c'est très certainement la raison pour laquelle la communauté offre déjà un bon nombre de packages.
+
+Comme pour toute librairie, inutile de réinventer la roue et de créer des doublons dans les packages, l'idée est vraiment d'ajouter des fonctionnalités à Atom afin d'enrichir notre expérience utilisateur d'Atom.
diff --git a/_posts/2016-12-05-create-atom-package.md b/_posts/2016-12-05-create-atom-package.md
new file mode 100644
index 000000000..def16518f
--- /dev/null
+++ b/_posts/2016-12-05-create-atom-package.md
@@ -0,0 +1,268 @@
+---
+layout: post
+title: Create your first Atom package
+permalink: /en/create-atom-package/
+author: vcomposieux
+date: '2016-12-05 17:34:21 +0100'
+date_gmt: '2016-12-05 16:34:21 +0100'
+categories:
+    - Javascript
+tags:
+    - atom
+    - babel
+    - jasmine
+    - package
+---
+# Introduction to Atom
+[Atom](https://atom.io) is an open-source text editor (mostly used by developers) which is multi-platform and developed by GitHub company. It is based on Electron, the Github-developed framework, which allows developers to build native desktop applications for any operating systems by writing Javascript code.
+
+The main interesting feature of Atom is that it also has a great package management tool and packages are also written in Javascript so it's quite easy for anyone to create one. This article aims to talk about it.
+Finally, its community is also active as it already has a lot of available packages: `5 285` at this time.
+You can browse all packages by going to the following address: [https://atom.io/packages](https://atom.io/packages).
+
+However, if you cannot find a package that fits your needs you can start creating your own and we will see how simple it is.
+
+# Generate your first package
+
+In order to create your own package, don't worry, you will not start from scratch. Indeed, we will use the `Package Generator`  command which is brought to us by Atom core.
+To do that, you will just have to navigate into  `Packages` -> `Package Generator` -> `Generate Atom Package`.
+
+In order to generate your package, you can choose the language between `Javascript`  and `Coffeescript` . This article will use Javascript.
+
+When the command is executed, Atom will open a new window into your package project, by default named `my-package`.
+
+# Package structure
+
+We will now see in details what's inside our package project directory:
+
+```
+├── CHANGELOG.md
+├── LICENSE.md
+├── README.md
+├── keymaps
+│   └── my-package.json         <- Key shortcuts registered by your package
+├── lib
+│   ├── my-package-view.js
+│   └── my-package.js           <- Entry point of your package
+├── menus
+│   └── my-package.json         <- Menus declaration of your package into Atom application
+├── package.json                <- Description and library dependencies of your package
+├── spec                        <- Tests directory (Jasmine) of your package
+│   ├── my-package-spec.js
+│   └── my-package-view-spec.js
+└── styles                      <- Stylesheets used by your package
+└── my-package.less
+```
+
+The first element to add to your package is the `package.json`  file which has to contain all information of your package such as its name, version, license type, keywords that will enable you to find your package into Atom registry and also your package dependancies.
+
+Please also note that there is a section called `activationCommands`  which allows to define the executed command when your package is loaded.
+
+Next, we have the `keymaps/my-package.json`  file which allows you to define shortcuts into your package very easily. Here is the default example:
+
+```json
+{
+  "atom-workspace": {
+    "ctrl-alt-p": "my-package:toggle"
+  }
+}
+```
+
+Next, we will go into your package entry point. It is located into `lib/my-package.js` file.
+This file exports a default object which contains a `subscriptions`  property and also `activate()`  and `deactivate()`  methods.
+
+During package activation (inside `activate()` method), we will register a CompositeDisposable type object inside our `subscriptions`  property and that will allow us to add and maybe later remove some commands offered by our package:
+
+```js
+activate(state) {
+  this.subscriptions = new CompositeDisposable();
+  this.subscriptions.add(atom.commands.add('atom-workspace', {
+    'my-package:toggle': () => this.toggle()
+  }));
+}
+```
+
+Now that our command is registered, we can test it by simply typing the following words, into the Atom command palette: `My Package: Toggle`.
+This command will execute the code contained in the `toggle()`  method of the class and will display a little modal at the top of the window.
+You can add as many commands as you want and I really encourage you to decouple your code.
+
+# Add settings for your package
+
+The [Config](https://atom.io/docs/api/latest/Config) component allows your package to have some settings.
+
+To add a new setting, you just have to define a `config`  property into your package's class which is an object containing each settings definition, as follows:
+
+```json
+config: {
+  "gitlabUrl": {
+    "description": "If you rely on a private Gitlab server, please type your base URI here (default: https://gitlab.com).",
+    "type": "string",
+    "default": "https://gitlab.com"
+  }
+}
+```
+
+Atom settings allow multiple setting types (`boolean` , `color` , `integer` , `string` , ...) so it can fit your needs on setting values by your users.
+
+Once it is added, if you reload your package, you will see your package settings appearing into Atom settings:
+
+Atom - Settings
+
+In order to retrieve the value (or default value) defined by a user for a given setting in your code, you just have to use the following line:
+
+```js
+let gitlabUrl = atom.config.get('gitlabUrl');
+```
+
+# Components overview
+
+So you are now ready to develop your package. We will have a quick overview of some interesting components that Atom brings to you and allows you to use in your package.
+
+## TextEditor: Interact with the text editor
+
+With the `TextEditor` component, you will be able to insert some text into user's text editor, to save the current file, to go back and forth the history, to move the cursor into editor, to copy/paste into clipboard, to play with line indentation, to scroll, and to do so much more...
+
+Here are some examples to insert text in a specific position and to save the file automatically:
+```js
+editor.setCursorBufferPosition([row, column]);
+
+editor.insertText('foo');
+editor.save();
+```
+
+## ViewRegistry & View: Create and display your own window
+
+These components allow you to create views (modals / windows) inside Atom and display them.
+
+You have an example of a modal `View` into the default package:
+
+```js
+export default class MyPackageView {
+    constructor(serializedState) {
+      // Create root element
+      this.element = document.createElement('div');
+      this.element.classList.add('my-package');
+
+      // Create message element
+      const message = document.createElement('div');
+      message.textContent = 'The MyPackage package is Alive! It\'s ALIVE!';
+      message.classList.add('message');
+      this.element.appendChild(message);
+    }
+
+    // ...
+}
+
+let myPackageView = new MyPackageView(state.myPackageViewState);
+let modalPanel = atom.workspace.addModalPanel({
+item: myPackageView.getElement(),
+visible: false;
+});
+
+modalPanel.show();
+```
+
+## NotificationManager & Notification: Alert your users with notifications
+
+Your package can also display a variety of notifications from "success" to "fatal error":
+
+```js
+atom.notifications.addSuccess('My success notification');
+atom.notifications.addInfo('My info notification');
+atom.notifications.addWarning('My warning notification');
+atom.notifications.addError('My error notification');
+atom.notifications.addFatalError('My fatal error notification');
+```
+
+## GitRepository
+
+This one is also really interesting: indeed, you can access all the git properties of the current git repository that is used.
+This way, you will be able to access the current branch name, the repository remote URL and also see if a file is considered as a new or modified file. Let's see it in action:
+
+```js
+let repository = atom.project.getRepositoryForDirectory('/path/to/project');
+
+console.log(repository.getOriginURL());               // -> git@github.com:eko/atom-pull-request.git
+console.log(repository.getShortHead());               // -> master
+console.log(repository.isStatusNew('/path/to/file')); // -> true
+```
+
+## And more things to discover...
+
+We just made a review of the components that I played with but I invite you to read more on the following link if you want to go further: [https://atom.io/docs/api/latest/AtomEnvironment](https://atom.io/docs/api/latest/AtomEnvironment).
+
+## Test your package with specs
+
+Our package is now developed but we don't have to forget about the tests. To do that, Atom uses [Jasmine](https://jasmine.github.io).
+
+Your default package already has a prepared test file:
+
+```js
+import MyPackageView from '../lib/my-package-view';
+
+describe('MyPackageView', () => {
+  it('has one valid test', () => {
+    expect('life').toBe('easy');
+  });
+});
+```
+
+Jasmine specs tests are written in the following way:
+
+* `describe()` : A Jasmine test suite starts with a "describe" function which takes a name as the first argument and a function as the second,
+* `it()` : A specification is added by using this function, "it" has to be contained into a specification,
+* `expect()` : This one is an assertion, when we expect something to happen.
+
+This is now your turn to play with Jasmine and test your package logic.
+In order to run the specs tests, you just have to navigate into the following menu: `View`  -> `Packages`  -> `Run Package Specs`.
+
+# Publish your package
+
+Our package is now ready to be deployed! Let's send it.
+
+Atom - Fusée
+
+To do that, we will use the `apm`  CLI tool which comes with Atom when installing it.
+
+After pushing your code into a Github repository, simply go into your package directory and type the following command:
+
+```bash
+$ apm publish --tag v0.0.1 minor
+
+Preparing and tagging a new version ✓
+Pushing v0.0.1 tag ✓
+...
+```
+
+This command will be in charge of creating the new version tag into repository and publish this version into the Atom registry.
+Congratulations, your package is now published and available on the following URL: `https://atom.io/packages/`!
+
+# Continuous Integration
+
+The final step is to ensure that your package will continue to work in the future when you or your contributors will add new features but also when Atom releases a new beta version. To do that, you can use [Travis-CI](https://travis-ci.org) on your repository with the following configuration:
+
+```yaml
+language: objective-c
+
+notifications:
+  email:
+    on_success: never
+    on_failure: change
+
+script: 'curl -s https://raw.githubusercontent.com/nikhilkalige/docblockr/develop/spec/atom-build-package.sh | sh'
+
+env:
+  global:
+    - APM_TEST_PACKAGES=""
+
+  matrix:
+    - ATOM_CHANNEL=stable
+    - ATOM_CHANNEL=beta
+```
+
+# Conclusion
+
+I personally think that this is a little revolution to allow developers to make their own editor and bring the features they want.
+
+Moreover, the Atom API is already very rich and very simple to use and this is certainly the main reason why the community offers a large number of packages.
+To conclude, as for all libraries, it is not useful to reinvent the wheel by creating already existing packages. The idea is to add features if they don't already exists, in order to enrich your user experience.
diff --git a/_posts/2017-01-17-redux-structurez-vos-applications-front.md b/_posts/2017-01-17-redux-structurez-vos-applications-front.md
new file mode 100644
index 000000000..536f181be
--- /dev/null
+++ b/_posts/2017-01-17-redux-structurez-vos-applications-front.md
@@ -0,0 +1,254 @@
+---
+layout: post
+title: 'Redux : Structurez vos applications front'
+permalink: /fr/redux-structurez-vos-applications-front/
+author: vcomposieux
+date: '2017-01-17 10:09:00 +0100'
+date_gmt: '2017-01-17 09:09:00 +0100'
+categories:
+    - Javascript
+tags:
+    - Javascript
+    - react
+    - redux
+    - vuejs
+---
+L'écosystème Javascript est très riche, beaucoup de développeurs mais aussi de frameworks et d'outils sont disponibles.
+Lorsque vous souhaitez développer une application, quel que soit son framework de rendu, vous allez vite être amené à vouloir architecturer votre projet afin de différencier et d'organiser les données des vues. C'est particulièrement le cas lorsque vous utilisez des frameworks de rendu de composants comme `React` ou `VueJS`.
+
+Historiquement, le besoin s'est fait sentir sur [React](https://facebook.github.io/react/) et Facebook a donc ouvert les sources de son outil [Flux](http://facebook.github.io/flux/).
+
+Le principe est le suivant :
+
+
+
+Votre application déclare, pour chaque composant, les `actions`  qui lui sont liées. Ces actions permettent de définir l'état de votre composant, stocké dans un `store` , qui permet de maintenir votre `vue`  à jour.
+L'inconvénient est que dans ce cas, vous avez un store par composant. Ce modèle fonctionne pour React mais vous pouvez vous sentir limité sur certaines applications.
+Dan Abramov a donc lancé, en juin 2015, [Redux](http://redux.js.org), qui permet principalement de simplifier la gestion du store car il y a en effet qu'un seul store pour toute votre application dans Redux.
+
+Tous vos composants peuvent donc accéder à vos données.
+
+Pour plus d'informations sur les différences Redux / Flux, je vous invite à lire cette [réponse de Dan](http://stackoverflow.com/questions/32461229/why-use-redux-over-facebook-flux/32920459#32920459).
+
+# Installation
+
+Nous allons voir dans cet article comment mettre en place et utiliser Redux sur vos projets.
+Notez dès maintenant que la librairie peut être utilisée avec plusieurs librairies de rendu comme React ou VueJS.
+Pour installer Redux, il vous faudra installer le package npm (ou yarn) `redux`.
+
+Si vous utilisez Redux sur une application React, il vous faudra également le package `react-redux`  ou encore `vue-redux`  s'il s'agit d'un projet VueJS.
+
+```bash
+$ yarn add redux
+```
+
+Rien de plus, vous êtes prêt à utiliser Redux.
+
+# Utilisation classique
+
+Comme décrit précédemment, il vous faudra initialiser un `store`  qui va permettre de stocker l'état de votre application.
+
+Pour instancier ce store, il vous faudra passer un ou plusieurs `reducers` . Les reducers contiennent les méthodes qui effectuent le changement d'état de votre application.
+
+Ces changements d'état sont effectués lorsqu'une `action`  est déclenchée sur votre application.
+Voilà, nous avons là les 3 composantes d'une application structurée par Redux : des `actions`, des `reducers` et un `store`.
+Nous allons prendre un cas pratique simple : un compteur que l'on peut incrémenter ou décrémenter d'une certaine valeur.
+
+Voici l'arborescence que nous ciblons :
+
+```
+src/
+├── actions
+│   └── counter.js
+├── constants
+│   └── ActionTypes.js
+├── reducers
+│   ├── another.js
+│   ├── counter.js
+│   └── index.js
+└── store
+    └── configureStore.js
+```
+
+## Actions
+
+Écrivons donc un fichier d'actions qui permet de définir ces deux actions : incrémenter et décrémenter.
+Avant tout, nous allons également stocker ces noms d'actions dans des constantes, ce qui nous permettra d'être clair dans notre code car nous ferons toujours appel à ces constantes.
+Créez donc un fichier `src/constants/ActionTypes.js`  avec le contenu :
+
+```js
+export const INCREMENT = 'INCREMENT';
+export const DECREMENT = 'DECREMENT';
+```
+
+Nous allons maintenant écrire les définitions des actions. Créez maintenant le fichier `src/actions/counter.js` :
+
+```js
+import * as types from '../constants/ActionTypes';
+
+export const increment = (value) => ({ type: types.INCREMENT, value });
+export const decrement = (value) => ({ type: types.DECREMENT, value });
+```
+
+Vous venez de déclarer deux actions (`increment`  et `decrement` ) qui prennent chacune un type (obligatoire) et une valeur à ajouter ou soustraire.
+
+## Reducers
+
+Il nous faut maintenant écrire les méthodes des reducers permettant de mettre à jour l'état de notre application.
+Ces reducers seront écrits dans le fichier `src/reducers/counter.js` :
+
+```js
+import { INCREMENT, DECREMENT } from '../constants/ActionTypes';
+
+const initialState = {
+  current: 0,
+};
+
+export default function counter(state = initialState, action) {
+  switch (action.type) {
+    case INCREMENT:
+      return {
+        current: state.current += action.value,
+      };
+
+    case DECREMENT:
+      return {
+        current: state.current -= action.value,
+      };
+
+    default:
+      return state;
+  }
+}
+```
+
+Vous avez compris l'idée, nous avons nos actions dans un `switch() { case ... }`  et mettons directement à jour les valeurs de notre store.
+Vous remarquerez que nous avons créés un état initial (initialState) afin d'initialiser les valeurs de notre application.
+
+`Note :` Il vous est possible de créer autant de reducers que nécessaire.
+
+Si vous avez déclaré plusieurs reducers dans votre application, vous pouvez les combiner dans un fichier `src/reducers/index.js` comme suit :
+
+```js
+import { combineReducers } from 'redux';
+
+import counter from './counter';
+import another from './another';
+
+const reducers = combineReducers({
+  counter,
+  another,
+});
+
+export default reducers;
+```
+
+## Store
+
+Maintenant que nous avons nos actions et reducers, dernière étape indispensable : la création du store !
+
+Créez un fichier `src/store/configureStore.js`  avec le contenu suivant :
+
+```js
+import { createStore } from 'redux';
+import reducers from '../reducers';
+
+const configureStore = () => {
+  return createStore(
+    reducers,
+  );
+};
+
+export default configureStore;
+```
+
+Nous utilisons ici la fonction `createStore()` de l'API Redux permettant de créer notre store.
+
+Afin d'aller un peu plus loin, notez que cette fonction peut prendre jusqu'à 3 arguments :
+
+* un ou des reducers,
+* un état pré-chargé (optionnels), correspondant à un état initial,
+* des "enhancers" (optionnels), autrement dit des callbacks comme des middlewares.
+
+Un middleware permet d'exécuter une callback à chaque fois que le `dispatch()`  d'actions est exécuté.
+
+Voici un exemple de middleware permettant de logger chaque action déclenchée :
+
+```js
+import { createStore, applyMiddleware } from 'redux'
+import reducers from '../reducers';
+
+function logger({ getState }) {
+  return (next) => (action) => {
+    console.log('will dispatch', action)
+    return next(action)
+  }
+}
+
+const configureStore = () => {
+  return createStore(
+    reducers,
+    applyMiddleware(logger)
+  );
+};
+
+export default configureStore;
+```
+
+N'oubliez pas d'utiliser la fonction `applyMiddleware()`  lorsque vous passez vos fonctions de middleware au store.
+
+# Utilisation avec React
+
+Le principe reste exactement le même lorsque Redux est utilisé avec React, cependant, la librairie `react-redux`  va vous apporter des petites choses en plus.
+Vous allez en effet pouvoir lier l'état de votre application gérée par Redux ainsi que les actions que vous avez définies avec les `props`  de vos composants React.
+
+Prenons un composant `Counter`  reflétant l'architecture Redux mise en place dans notre cas d'exemple :
+
+```js
+import React, { PropTypes } from 'react';
+import { connect } from 'react-redux';
+import { bindActionCreators } from 'redux';
+
+import * as CounterActions from '../actions/counter';
+
+const Counter = ({ children, value, actions }) => (
+  
+ + +
+); + +Counter.propTypes = { + children: PropTypes.object.isRequired, + value: PropTypes.number.isRequired, + actions: PropTypes.object.isRequired, +}; + +const mapStateToProps = state => ({ + value: state.counter.current, +}); + +const mapDispatchToProps = dispatch => ({ + actions: bindActionCreators(CounterActions, dispatch), +}); + +export default connect( + mapStateToProps, + mapDispatchToProps, +)(Counter); +``` + +De cette façon, nous récupérons donc les valeurs de nos props provenant de notre store mais également une propriété `actions`  permettant d'appeler nos actions Redux. +Les principaux éléments à noter ici sont : + +* `mapStateToProps`  est une fonction permettant de mapper des `valeurs de notre state` Redux avec des `propriétés React`, +* `mapDispatchToProps`  est une fonction permettant de mapper des `actions` Redux avec des `propriétés React`. + +Ces deux fonctions sont ensuite appliquées à l'aide de la fonction `connect()`  fournie par `react-redux`. + +`Note :` Nous devons ici utiliser `bindActionCreators()`  sur nos `CounterActions`  car il s'agit d'un objet dont les valeurs sont des actions et cette fonction va permettre d'ajouter un appel à la fonction `dispatch()`  de Redux afin que celles-ci soient correctement déclenchées. + +# Conclusion + +Si nous mettons en parallèle les `1 303 720 téléchargements sur le mois précédent` `de la librairie Redux` avec les `2 334 221 de téléchargements pour React`, nous remarquons que Redux est aujourd'hui `très utilisé` et semble vraiment très `apprécié` par les développeurs car il s'agit d'une solution `simple` qui permet réellement de structurer une application front. +Redux apporte, selon moi, une `vraie solution` permettant de structurer des applications au métier complexe aux communautés comme React, VueJS mais également aux autres. diff --git a/_posts/2017-01-20-redux-structure-frontend-applications.md b/_posts/2017-01-20-redux-structure-frontend-applications.md new file mode 100644 index 000000000..c1127d148 --- /dev/null +++ b/_posts/2017-01-20-redux-structure-frontend-applications.md @@ -0,0 +1,254 @@ +--- +layout: post +title: 'Redux: Structure your frontend applications' +permalink: /en/redux-structure-frontend-applications/ +author: vcomposieux +date: '2017-01-20 12:12:34 +0100' +date_gmt: '2017-01-20 11:12:34 +0100' +categories: + - Javascript +tags: + - Facebook + - Javascript + - react + - redux +--- +Javascript ecosystem is really rich: full of developers but also full of frameworks and libraries. + +When you want to develop a frontend application, whatever its rendering framework, you will have to structure things into your project in order to organize the data management with views. This case occurs particularly when you use component rendering frameworks like `React` or `VueJS`. +Historically, this has been needed by [React](https://facebook.github.io/react/) so that's why Facebook has open sourced its tool named [Flux](http://facebook.github.io/flux/). + +Here is the philosophy: + + + +Your application declare `actions`  for each components. These actions allow you to define the state of your data which is stored in a `store` . This stores continually maintains your `view`  up-to-date. +We have a drawback in this case because you have to define one store per component. This is working but on large applications you can feel limited with it. +In June 2015, Dan Abramov has launched [Redux](http://redux.js.org/) which simplify store management because you only have one store for all your application. + +All of your application components can access to the whole state. + +For more information about Redux/Flux differences I encourage you to have a look at [Dan's answer](http://stackoverflow.com/questions/32461229/why-use-redux-over-facebook-flux/32920459#32920459) on this subject. + +# Installation + +This article will deal about how to install and use Redux on your own projects. +Please keep in mind that Redux can be used with multiple rendering frameworks like React or VueJS. +To install Redux, you will just need the `redux` npm (or yarn) package. + +If you use Redux into a React application, you will also need the `react-redux`  package or even the `vue-redux`  if you want to use it on a VueJS project. + +```bash +$ yarn add redux +``` + +Nothing more, you can now start to use Redux. + +# Basic usage + +As previously described, you will have to instanciate a new `store`  that will allow to store the state of all your application. +In order to instanciate this store, you will have to give to it some `reducers` . Reducers contain methods that change the state of your application. +These state changes occur when an `action`  is dispatched by your application. + +Here we are, we have the 3 things needed by a Redux application: `actions`, `reducers` and a `store`. +We will use a simple practical case: a counter that we can increment or decrement with a given value. + +Here is our target arborescence: + +``` +src/ +├── actions +│   └── counter.js +├── constants +│   └── ActionTypes.js +├── reducers +│   ├── another.js +│   ├── counter.js +│   └── index.js +└── store + └── configureStore.js +``` + +## Actions + +Let's write an actions containing file that will implement our 2 actions: increment and decrement. +Before all, we will store these actions names into constants in order to keep our code clear and comprehensible as we will always call these constants in all of our code. + +Start by creating a `src/constants/ActionTypes.js`  file with the following content: + +```js +export const INCREMENT = 'INCREMENT'; +export const DECREMENT = 'DECREMENT'; +``` + +Great, we will now write actions that correspond to these constants in a `src/actions/counter.js` file: + +```js +import * as types from '../constants/ActionTypes'; + +export const increment = (value) => ({ type: types.INCREMENT, value }); +export const decrement = (value) => ({ type: types.DECREMENT, value }); +``` + +You have just created your 2 actions (`increment`  and `decrement`) which each have a type property (required) and a value to add or remove to the current counter value. + +## Reducers + +We will now write reducers functions that correspond to the actions we previously wrote in order to update the value in our application state. + +This will be written in the `src/reducers/counter.js` file: + +```js +import { INCREMENT, DECREMENT } from '../constants/ActionTypes'; + +const initialState = { + current: 0, +}; + +export default function counter(state = initialState, action) { + switch (action.type) { + case INCREMENT: + return { + current: state.current += action.value, + }; + + case DECREMENT: + return { + current: state.current -= action.value, + }; + + default: + return state; + } +} +``` + +You got the idea, we have our actions wrapped into a `switch() { case ... }`  and directly return the store updated with new values. +You can also observe that we have initialized an initial state (initialState) in order to prepare our application state with some default values. + +`Note:` You can write as many reducers as you need in your application so you can clearly split your code application. + +Only point if you declare multiple reducers into your application is that you will have to combine them here in a file named `src/reducers/index.js`  as follows: + +```js +import { combineReducers } from 'redux'; + +import counter from './counter'; +import another from './another'; + +const reducers = combineReducers({ + counter, + another, +}); + +export default reducers; +``` + +## Store + +You have your actions and your reducers so let's dive into the final step: store creation! +Store will be created in a `src/store/configureStore.js`  file with only these couple of lines: + +```js +import { createStore } from 'redux'; +import reducers from '../reducers'; + +const configureStore = () => { + return createStore( + reducers, + ); +}; + +export default configureStore; +``` + +You just have to call the Redux's `createStore()`  API function in order to create your store. +In order to go further, please note that this function can take a maximum of 3 arguments: + +* one or many combines reducers, +* a pre-loaded state (*optional*), corresponding to an initial state, +* some "enhancers" (*optionals*), which are some callbacks such as middlewares. + +A middleware is a callback that is executed each time Redux can the `dispatch()`  function so each time an action is triggered. + +Here is a simple middleware that logs each dispatched actions: + +```js +import { createStore, applyMiddleware } from 'redux' +import reducers from '../reducers'; + +function logger({ getState }) { + return (next) => (action) => { + console.log('will dispatch', action) + return next(action) + } +} + +const configureStore = () => { + return createStore( + reducers, + applyMiddleware(logger) + ); +}; + +export default configureStore; +``` + +Do not forget to call the `applyMiddleware()` function when you pass your function to the store argument. + +# React use case + +Principles are exactly the same when you want to use Redux on a React application. However, the `react-redux`  library brings some cool additional features to fit with React. +Indeed, thanks to this library, you will be able to map your React components `props`  with the Redux state and actions. + +Let's take a concrete case: a `Counter`  component which could be a component for our previous use case: + +```js +import React, { PropTypes } from 'react'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; + +import * as CounterActions from '../actions/counter'; + +const Counter = ({ children, value, actions }) => ( +
+ + +
+); + +Counter.propTypes = { + children: PropTypes.object.isRequired, + value: PropTypes.number.isRequired, + actions: PropTypes.object.isRequired, +}; + +const mapStateToProps = state => ({ + value: state.counter.current, +}); + +const mapDispatchToProps = dispatch => ({ + actions: bindActionCreators(CounterActions, dispatch), +}); + +export default connect( + mapStateToProps, + mapDispatchToProps, +)(Counter); +``` + +This way, we are able to retrieve our props values which came from the Redux store but also an `actions` property that will allow us to dispatch Redux events when we will call it. + +Main things to note here are: + +* `mapStateToProps`  is a function that allows to map our Redux **state properties** with `React properties`, +* `mapDispatchToProps`  is a function that allows to map Redux `actions` with `React properties`. + +These two functions are applied thanks to the `connect()`  function brought by the `react-redux` library. + +`Note:` We have to use the `bindActionCreators()`  function over our `CounterActions`  because this is an object that contains actions functions so this function will allows React to call the `dispatch()`  Redux function when React will call the functions in order to have them correctly triggered. + +# Conclusion + +If we put in parallel the download numbers of Redux (`1 303 720 download over the previous month)` with the `2 334 221 downloads of React`, we can conclude that Redux is today `very used` and seems very much `appreciated` by developers because it's a `simple` solution that can greatly help you to structure your application. +Redux brings, in my opinion, a `real solution` to structure complex (or large) business applications and bring that to the React and VueJS (and others) communities. diff --git a/_posts/2017-02-22-consul-service-discovery-failure-detection-2.md b/_posts/2017-02-22-consul-service-discovery-failure-detection-2.md new file mode 100644 index 000000000..fbf4ec8c7 --- /dev/null +++ b/_posts/2017-02-22-consul-service-discovery-failure-detection-2.md @@ -0,0 +1,285 @@ +--- +layout: post +title: 'Consul : Service Discovery et Failure Detection' +permalink: /fr/consul-service-discovery-failure-detection-2/ +author: vcomposieux +date: '2017-02-22 10:49:25 +0100' +date_gmt: '2017-02-22 09:49:25 +0100' +categories: + - Dev Ops +tags: + - service + - consul + - discovery + - failure + - detection + - health + - check +--- +# Introduction + +Consul est un outil développé en Go par la société HashiCorp et a vu le jour en 2013. +Consul a plusieurs composants mais son objectif principal est de regrouper la connaissance des services d'une architecture (service discovery) et permet de s'assurer que les services contactés sont toujours disponibles en s'assurant que la santé de ces services est toujours bonne (via du health check). + +Concrètement, Consul va nous apporter un serveur DNS permettant de mettre à jour les adresses IP disponibles pour un service, en fonction de ceux qui sont en bonne santé. Ceci permet également de faire du load balancing bien que nous verrons qu'il ne permette pas pour le moment de préférer un service à un autre. +Il offre également d'autres services tel que du stockage clé/valeur, nous l'utiliserons dans cet article afin que Docker Swarm y stocke ses valeurs. + +Afin de clarifier la suite de cet article, voici les ports utilisés par Consul : + +* `8300` (+ `8301` et `8302`) : Echanges via RPC, +* `8400` : Echanges via RPC par le CLI, +* `8500` : Utilisé pour l'API HTTP et l'interface web, +* `8600` : Utilisé pour le serveur DNS. + +La suite de cet article va se concentrer sur la partie service discovery et failure detection. Nous allons pour cela mettre en place un cluster Docker Swarm possédant l'architecture suivante : + + + +Nous aurons donc 3 machines Docker : + +* Une machine avec `Consul` (Swarm Discovery), +* Une machine étant notre "`node 01`" avec un service HTTP (Swarm), +* Une machine étant notre "`node 02`" avec un service HTTP (Swarm). + +Nous mettrons également sur nos deux nodes (cluster Docker Swarm) un container Docker pour Registrator, permettant de faciliter l'enregistrement de nos services Docker sur Consul. + +Pour plus d'informations concernant `Registrator`, vous pouvez vous rendre sur : [https://gliderlabs.com/registrator/](https://gliderlabs.com/registrator/) +Commençons à installer notre architecture ! + +# Service discovery + +## Première machine : Consul (Swarm Discovery) + +Nous allons commencer par créer la première machine : notre Consul. + +Pour cela, tapez : + +```bash +$ docker-machine create -d virtualbox consul +``` + +Une fois la machine prête, préparez votre environnement pour utiliser cette machine et lancez un container Consul : + +```bash +$ eval $(docker-machine env consul) +$ docker run -d \ + -p 8301:8301 \ + -p 8302:8302 \ + -p 8400:8400 \ + -p 8500:8500 \ + -p 53:8600/udp \ + consul +``` + +Nous avons maintenant notre Consul prêt à recevoir nos services et nos prochaines machines membres de notre cluster Docker Swarm. + +Vous pouvez d'ailleurs ouvrir l'interface web mise à disposition en obtenant l'ip de la machine Consul : + +```bash +$ docker-machine ip consul + +``` + +Puis ouvrez dans votre navigateur l'URL : `http://:8500`. + +## Deuxième machine : Node 01 + +Nous allons maintenant créer la machine correspondant au premier node de notre cluster Docker Swarm qui se verra également obtenir le rôle de master de notre cluster Swarm (il en faut bien un). + +```bash +$ docker-machine create -d virtualbox \ + --swarm \ + --swarm-master \ + --swarm-discovery="consul://$(docker-machine ip consul):8500" \ + --engine-opt="cluster-store=consul://$(docker-machine ip consul):8500" \ + --engine-opt="cluster-advertise=eth1:2376" swarm-node-01 +``` + +Comme vous le voyez, nous précisons l'option `--swarm-discovery`  avec l'IP de notre machine Consul et le port 8500 correspondant à l'API de Consul. Ainsi, Docker Swarm pourra utiliser l'API pour enregistrer les machines du cluster. + +Nous allons maintenant configurer notre environnement pour utiliser cette machine et y installer dessus un container Registrator permettant d'auto-enregistrer les nouveaux services sur Consul. + +Pour ce faire, tapez : + +```bash +$ eval $(docker-machine env swarm-node-01)
+``` + +puis : + +```bash +$ docker run -d \ + --volume=/var/run/docker.sock:/tmp/docker.sock \ + gliderlabs/registrator \ + -ip $(docker-machine ip swarm-node-01) \ + consul://$(docker-machine ip consul):8500 +``` + +Vous remarquez que nous partageons le socket Docker sur la machine. Cette solution peut être [controversée](https://www.lvh.io/posts/dont-expose-the-docker-socket-not-even-to-a-container.html mais dans le cas de cet article, passons là-dessus. Pour une architecture stable, nous préférerons enregistrer nous-même les services via l'API de Consul. +L'option `-ip`  permet de préciser à Registrator l'IP sur laquelle nous voulons accéder aux services, à savoir l'IP de la machine et non pas l'IP interne du container Docker. + +Nous sommes prêts à démarrer notre service HTTP. Celui-ci est une simple image Docker "ekofr/http-ip" qui lance une application HTTP écrite en Go et qui affiche "hello, " avec l'adresse IP du container courant. + +Pour le besoin de cet article, nous allons également créer un réseau différent entre les deux machines afin d'identifier des adresses IP différentes pour les deux services. + +Créons donc un nouveau réseau pour notre node 01 : + +```bash +$ docker network create \ + --subnet=172.18.0.0/16 network-node-01 +``` + +puis utilisez ce réseau sur le container du service HTTP : + +```bash +$ docker run -d \ + --net network-node-01 \ + -p 80:8080 \ + ekofr/http-ip +``` + +Avant d'exécuter les mêmes étapes pour créer notre node 02, assurons-nous d'avoir un service fonctionnel : + +```bash +$ curl http://localhost:80 +hello from 172.18.0.X +``` + +## Troisième machine : Node 02 + +Nous allons donc (presque) répéter les étapes du node 01 en changeant quelques valeurs seulement. Créez la machine : + +```bash +$ docker-machine create -d virtualbox \ + --swarm \ + --swarm-discovery="consul://$(docker-machine ip consul):8500" \ + --engine-opt="cluster-store=consul://$(docker-machine ip consul):8500" \ + --engine-opt="cluster-advertise=eth1:2376" swarm-node-02 +``` + +Préparez votre environnement pour utiliser cette machine node 02 et installez-y Registrator : + +```bash +$ eval $(docker-machine env swarm-node-02) +``` + +```bash +$ docker run -d \ + --volume=/var/run/docker.sock:/tmp/docker.sock \ + gliderlabs/registrator \ + -ip $(docker-machine ip swarm-node-02) \ + consul://$(docker-machine ip consul):8500 +``` + +Puis créez un nouveau réseau et lancez le service HTTP avec ce réseau : + +```bash +$ docker network create \ + --subnet=172.19.0.0/16 network-node-02 +``` + +```bash +$ docker run -d \ + --net network-node-02 \ + -p 80:8080 \ + ekofr/http-ip +```bash + +Nous voilà prêt à découvrir ce que nous apporte Consul. + +# Requêtes DNS + +Vous pouvez en effet maintenant résoudre votre service `http-ip.service.consul`  en utilisant le serveur DNS apporté par Consul, vous devriez voir vos deux services enregistrés : + +```bash +$ dig @$(docker-machine ip consul) http-ip.service.consul + +;; QUESTION SECTION: +;http-ip.service.consul. IN A + +;; ANSWER SECTION: +http-ip.service.consul. 0 IN A 192.168.99.100 +http-ip.service.consul. 0 IN A 192.168.99.102 +``` + +Autrement dit, un load balancing sera fait sur un de ces deux services lorsque vous chercherez à joindre `http://http-ip.service.consul`. +Oui, mais qu'en est-il du côté de la répartition de cette charge ? Pouvons-nous définir une priorité et/ou poids ? + +Malheureusement, la réponse est non, pas pour le moment. Une issue a cependant été ouverte sur Github pour demander le support de celui-ci : [https://github.com/hashicorp/consul/issues/1088](https://github.com/hashicorp/consul/issues/1088). + +En effet, si nous regardons de plus près l'enregistrement DNS de type `SRV` , voici ce que nous obtenons : + +```bash +$ dig @$(docker-machine ip consul) http-ip.service.consul SRV + +;; ANSWER SECTION: +http-ip.service.consul. 0 IN SRV 1 1 80 c0a86366.addr.dc1.consul. +http-ip.service.consul. 0 IN SRV 1 1 80 c0a86364.addr.dc1.consul. +``` + +Comme vous pouvez le voir, la priorité et le poids sont tous les deux définis à 1, le load balancing sera donc équilibré entre tous les services. + +Si vous ajoutez l'IP de la machine Consul en tant que serveur DNS sur votre système d'exploitation, vous pourrez donc appeler votre service en HTTP et vous rendre compte plus facilement du load balancing : + +```bash +$ curl http://http-ip.service.consul +hello from 172.18.0.2 + +$ curl http://http-ip.service.consul +hello from 172.19.0.2 +``` + +Nous avons ici une IP correspondant à chaque service HTTP que nous avons enregistré. + +# Failure detection + +Nous allons maintenant ajouter un Health Check à notre service afin de s'assurer que celui-ci peut être utilisé. + +Nous allons donc commencer par retourner sur notre node 01 et supprimer le container `ekofr/http-ip`  afin de le recréer avec un Health Check : + +```bash +$ eval $(docker-machine env swarm-node-01) +``` + +```bash +$ docker kill \ +$(docker ps -q --filter='ancestor=ekofr/http-ip') +``` + +Registrator nous offre des variables d'environnement afin d'ajouter des Health Check de nos containers à Consul, vous pouvez consulter la liste de toutes les variables disponibles ici : [http://gliderlabs.com/registrator/latest/user/backends/#consul](http://gliderlabs.com/registrator/latest/user/backends/#consul). + +L'idée est pour nous de vérifier que le port 80 répond correctement, nous allons donc ajouter un script exécutant simplement une requête curl. Pour ce faire : + +```bash +$ docker run -d \ + --net network-node-01 -p 80:8080 \ + -e SERVICE_CHECK_SCRIPT="curl -s -f http://$(docker-machine ip swarm-node-01)" \ + -e SERVICE_CHECK_INTERVAL=5s \ + -e SERVICE_CHECK_TIMEOUT=1s \ + ekofr/http-ip +``` + +Vous pouvez faire de même sur le node 02 (en faisant attention à bien modifier les `node-01`  en `node-02` ) et vous devriez maintenant pouvoir visualiser ces checks sur l'interface Consul : + + + +De la même façon, vous pouvez également utiliser l'API de Consul afin de vérifier la santé de vos services : + +```bash +$ curl http://$(docker-machine ip consul):8500/v1/health/checks/http-ip +[ + { + "Status": "passing", + "Output": "hello from 172.18.0.2", + "ServiceName": "http-ip", + }, + ... +] +``` + +# Conclusion + +Vous pouvez maintenant mettre en place Consul sur vos architectures afin de vous assurer que les services contactés sont bien disponibles mais surtout pouvoir identifier les éventuels problèmes qui peuvent survenir sur vos services. +Il est donc important d'ajouter un maximum de checks sur les éléments pouvant rendre vos services indisponibles (vérifier que celui-ci peut bien être contacté, vérifier l'espace disque disponible sur la machine, etc ...). + +Consul est un outil qui s'intègre parfaitement dans vos architectures, grâce à son utilisation très simple et son API complète. diff --git a/_posts/2017-02-22-consul-service-discovery-failure-detection.md b/_posts/2017-02-22-consul-service-discovery-failure-detection.md new file mode 100644 index 000000000..c8ec15647 --- /dev/null +++ b/_posts/2017-02-22-consul-service-discovery-failure-detection.md @@ -0,0 +1,287 @@ +--- +layout: post +title: 'Consul: Service Discovery and Failure Detection' +permalink: /en/consul-service-discovery-failure-detection/ +author: vcomposieux +date: '2017-02-22 10:50:16 +0100' +date_gmt: '2017-02-22 09:50:16 +0100' +categories: + - Dev Ops +tags: + - service + - consul + - discovery + - failure + - detection + - health + - check +--- +# Introduction +Consul is a product developed in Go language by the HashiCorp company and was born in 2013. + +Consul has multiple components but its main goal is to manage the services knowledge in an architecture (which is service discovery) and also allows to ensure that all contacted services are always available by adding health checks on them. +Basically, Consul will bring us a DNS server that will resolve IP addresses of a host's services, depending on which one will be healthy. + +This method also allows to do load balancing even if we will see in this blog post that it's actually not possible to distribute priorities between services. + +Another Consul service we'll use in this article is key/value storage because we'll create a Docker Swarm cluster and will use Consul as the discovery/storage for Docker Swarm. + +In order to clarify the rest of the article, here are the ports used by Consul: + +* `8300` (+ `8301` et `8302`): RPC exchanges, +* `8400`: RPC exchanges by the CLI, +* `8500`: Used for HTTP API and web interface, +* `8600`: Used for DNS server. + +Next, we'll focus on service discovery and failure detection. To do that, we'll create a Docker Swarm cluster with the following architecture: + + + +As you can see, we'll have 3 Docker machines: + +* A `Consul` machine (used for Swarm Discovery), +* A machine that will act as our "`node 01`" with an HTTP service that will run on it (Swarm), +* A machine that will act as our "`node 02`" with an HTTP service that will run on it (Swarm). + +We'll also add on our two nodes a Docker container with Registrator image that will facilitate the discovery of Docker containers into Consul. + +For more information about Registrator, you can visit: [https://gliderlabs.com/registrator/](https://gliderlabs.com/registrator/). +Let's start to install our architecture! + +# Service discovery + +## First machine: Consul (Swarm Discovery) + +We'll start by creating our first machine: Consul + +To do that, just type: + +```bash +$ docker-machine create -d virtualbox consul +``` + +Once the machine is fully ready, prepare your environment to use this Docker machine and launch a Consul container: + +```bash +$ eval $(docker-machine env consul) +$ docker run -d \ + -p 8301:8301 \ + -p 8302:8302 \ + -p 8400:8400 \ + -p 8500:8500 \ + -p 53:8600/udp \ + consul +``` + +Well, your Consul is ready to receive your services and also our Docker Swarm nodes! + +By the way, you can open the web interface by obtaining the Consul machine IP address: + +```bash +$ docker-machine ip consul + +``` + +Then, open in your browser the following URL: `http://:8500`. + +## Second machine: Node 01 + +Now, it's time to create t`he machine that corresponds to `our first Docker Swarm cluster node and that will also receive the master role for our cluster (we need one...): + +```bash +$ docker-machine create -d virtualbox \ + --swarm \ + --swarm-master \ + --swarm-discovery="consul://$(docker-machine ip consul):8500" \ + --engine-opt="cluster-store=consul://$(docker-machine ip consul):8500" \ + --engine-opt="cluster-advertise=eth1:2376" swarm-node-01 +``` + +As you can see, we've added the `--swarm-discovery`  option with our Consul machine IP address and port 8500 that corresponds to the Consul API. This way, Docker Swarm will use the Consul API to store machine information with the rest of the cluster. +We'll now configure our environment to use this machine and install a `Registrator`  container on top of it in order to auto-register our new services (Docker containers). + +To do that, type the following: + +```bash +$ eval $(docker-machine env swarm-node-01) +``` + +Then: + +```bash +$ docker run -d \ + --volume=/var/run/docker.sock:/tmp/docker.sock \ + gliderlabs/registrator \ + -ip $(docker-machine ip swarm-node-01) \ + consul://$(docker-machine ip consul):8500 +``` + +Here, you can notice that we share the host Docker socket in the container. This solution could be a [controversial](https://www.lvh.io/posts/dont-expose-the-docker-socket-not-even-to-a-container.html) solution but in our example case, forgive me about that ;) + +If you want to register services to Consul I recommend to register them using the Consul API in order to keep control on what's added in your Consul. + +The `-ip`  option allows to precise to Registrator that we want to register our services with the given IP address: so here the Docker machine IP address and not the Docker container internal IP address. +We are now ready to start our HTTP service. This one is located under the "ekofr/http-ip" Docker image which is a simple Go HTTP application that renders "hello, " with the IP address of the current container. + +In order to fit this article needs, we will also create a different network for the two Docker machines in order to clearly identify IP addresses for our two services. + +Let's create a new network concerning our first node: + +```bash +$ docker network create \ + --subnet=172.18.0.0/16 network-node-01 +``` + +then you can use the newly created network to be used with your HTTP service container: + +```bash +$ docker run -d \ + --net network-node-01 \ + -p 80:8080 \ + ekofr/http-ip +``` + +Before executing the same steps for our second node, we will ensure that our HTTP service works: + +```bash +$ curl http://localhost:80 +hello from 172.18.0.X +``` + +## Third machine: Node 02 + +We'll now repeat most steps we've ran for our first node but we'll change some values. First, create the Docker machine: + +```bash +$ docker-machine create -d virtualbox \ + --swarm \ + --swarm-discovery="consul://$(docker-machine ip consul):8500" \ + --engine-opt="cluster-store=consul://$(docker-machine ip consul):8500" \ + --engine-opt="cluster-advertise=eth1:2376" swarm-node-02 +``` + +Prepare your environment to use this new node 02 machine and install Registrator container on it: + +```bash +$ eval $(docker-machine env swarm-node-02) +``` + +```bash +$ docker run -d \ + --volume=/var/run/docker.sock:/tmp/docker.sock \ + gliderlabs/registrator \ + -ip $(docker-machine ip swarm-node-02) \ + consul://$(docker-machine ip consul):8500 +``` + +Now, create the new network and launch your HTTP service: + +```bash +$ docker network create \ + --subnet=172.19.0.0/16 network-node-02 +``` + +```bash +$ docker run -d \ + --net network-node-02 \ + -p 80:8080 \ + ekofr/http-ip +``` + +We are all set! We can now discover what Consul brings to us. + +# DNS Request + +Indeed, you can now resolve your service hostname `http-ip.service.consul` by using the DNS server brought by Consul and you should see your two services appearing as a DNS record: + +```bash +$ dig @$(docker-machine ip consul) http-ip.service.consul + +;; QUESTION SECTION: +;http-ip.service.consul. IN A + +;; ANSWER SECTION: +http-ip.service.consul. 0 IN A 192.168.99.100 +http-ip.service.consul. 0 IN A 192.168.99.102 +``` + +In other words, a kind of load balancing will be done on one of these services when you'll try to join them `http://http-ip.service.consul`. + +Ok, but what about the load balancing repartition? Are we able to define a priority and/or weight for each services? +Sadly, the answer is no, actually. An issue is currently opened about that on Github in order to bring this support. You can find it here: [https://github.com/hashicorp/consul/issues/1088](https://github.com/hashicorp/consul/issues/1088). + +Indeed, if we look in details about `SRV`  DNS record type, here is what we get: + +```bash +$ dig @$(docker-machine ip consul) http-ip.service.consul SRV + +;; ANSWER SECTION: +http-ip.service.consul. 0 IN SRV 1 1 80 c0a86366.addr.dc1.consul. +http-ip.service.consul. 0 IN SRV 1 1 80 c0a86364.addr.dc1.consul. +``` + +As you can see here, priority and weight are both defined to value 1 so the load balancing will be equal between all services. +If you add the Consul Docker machine IP address as a DNS server on your operating system, you'll be able to perform HTTP calls and see more easily what's happening on load balancing: + +```bash +$ curl http://http-ip.service.consul +hello from 172.18.0.2 + +$ curl http://http-ip.service.consul +hello from 172.19.0.2 +``` + +Here, we have an IP address that corresponds to each HTTP service that we have registered so we can clearly see that we are load balanced between our two containers. + +# Failure detection + +We'll now add a Health Check to our service in order to ensure that it can be use safely by our users. + +In this case we'll start to return on our node 01 and suppress the container named `ekofr/http-ip`  in order to recreate it with a Health Check: + +```bash +$ eval $(docker-machine env swarm-node-01) +``` + +```bash +$ docker kill \ + $(docker ps -q --filter='ancestor=ekofr/http-ip') +``` + +Registrator brings us some environment variables in order to add some Health Check for our containers into Consul and you can see the full list here: [http://gliderlabs.com/registrator/latest/user/backends/#consul](http://gliderlabs.com/registrator/latest/user/backends/#consul). + +Idea here is to verify that port 80 is opened and application answers correctly so we'll add a script that simply executes a curl command: + +```bash +$ docker run -d \ + --net network-node-01 -p 80:8080 \ + -e SERVICE_CHECK_SCRIPT="curl -s -f http://$(docker-machine ip swarm-node-01)" \ + -e SERVICE_CHECK_INTERVAL=5s \ + -e SERVICE_CHECK_TIMEOUT=1s \ + ekofr/http-ip +``` + +You can do the same thing on your node 02 (by paying attention to modify the `node-01`  values to `node-02` ) and you should now visualize these checks on the Consul web UI: + + + +You can also use the Consul API in order to verify the good health of your services: + +```bash +$ curl http://$(docker-machine ip consul):8500/v1/health/checks/http-ip +[ + { + "Status": "passing", + "Output": "hello from 172.18.0.2", + "ServiceName": "http-ip", + }, + ... +] +``` + +# Conclusion + +You are now able to install Consul on your projects architectures in order to ensure that services you contact are available and also be able to identify eventual issues that can occur on your services. + +It's important to add a maximum of checks on elements that can make your services become unavailable (ensure that this one can be contacted and answer, ensure that remaining available disk space is sufficient, etc...). +Consul is a tool that integrates well in your architectures by its simplicity of use and its powerful API. diff --git a/_posts/2017-04-11-http2-nest-pas-le-futur-cest-le-present.md b/_posts/2017-04-11-http2-nest-pas-le-futur-cest-le-present.md new file mode 100644 index 000000000..bcca12f7b --- /dev/null +++ b/_posts/2017-04-11-http2-nest-pas-le-futur-cest-le-present.md @@ -0,0 +1,166 @@ +--- +layout: post +title: "HTTP/2 n'est pas le futur. C'est le présent." +permalink: /fr/http2-nest-pas-le-futur-cest-le-present/ +author: vcomposieux +date: '2017-04-11 12:00:00 +0100' +date_gmt: '2017-04-11 12:00:00 +0100' +categories: + - Javascript + - Mobile + - Php +tags: + - compression + - header + - http + - http2 + - protocole + - tls +--- +Souvenez-vous, en `mai 1996`, la première version du protocole HTTP (HTTP/1.0) voit le jour. +Ce protocole est décrit sous forme de RFC et plus particulièrement sous la [RFC 1945](https://tools.ietf.org/html/rfc1945). + +Mais le temps a passé et les applications web ont énormément évoluées. Nous avons maintenant des applications front qui apportent de plus en plus de logique dans le navigateur et avons donc également de plus en plus d'assets à charger : de plus en plus de CSS avec des règles d'animations, parfois des opérations complexes définies en CSS, de plus en plus de fichiers Javascript, et enfin de plus en plus d'images. + +Si `HTTP/1.1` est sorti et nous a permis l'utilisation des nouvelles technologies que nous avons connues ces dernières années, l'usage de plus en plus intensif des smartphones et appareils connectés nécessite désormais d'améliorer les performances de chargement de nos applications HTTP. + +Après une première étape menée en 2009 par Google avec le protocole `SPDY`, c'est finalement dans ce sens que va `HTTP/2` et sa [RFC 7540](https://tools.ietf.org/html/rfc7540). + +# Introduction à HTTP/2 + +Aujourd'hui, le protocole HTTP/2 est supporté par la plupart des navigateurs. Au moment de l'écriture de cet article, seul Opera Mini se fait encore désirer : + + + +Il est donc possible d'envisager dès maintenant de passer vos applications web à HTTP/2 et ainsi offrir des performances de navigation accrues à vos visiteurs. + +En effet, HTTP/2 va vous permettre d'apporter les fonctionnalités que nous allons décrire dans la suite de ces article. + +## Support natif de TLS + +Si le chiffrement n'est `pas obligatoire`, certains navigateurs ne supportent HTTP/2 qu'avec la méthode de chiffrement TLS associée, et ce n'est pas plus mal car étant donné qu'il est de plus en plus simple d'obtenir un certificat SSL de nos jours, cela ne pose donc aucun soucis et vous permet de sécuriser davantage vos applications. + +Si vous n'utilisez pas le chiffrement, le diminutif donné au protocole est `h2c` . Ce sera  `h2`  si vous utilisez le chiffrement. + +Si vous souhaitez plus d'informations sur la configuration du protocole TLS afin d'[améliorer la sécurité des échanges SSL](https://vincent.composieux.fr/article/ameliorer-la-securite-des-echanges-ssl-effectues-par-votre-serveur), je vous invite à lire mon article sur le sujet. + +## Multiplexage de flux + +Si HTTP/1 chargeait les ressources les unes après les autres, comme en décrit la cascade de chargement des ressources d'une application ci-dessous, HTTP/2 va vous permettre de gagner du temps au niveau des états d'attente car plusieurs ressources pourront être directement déchargées dans le même flux de réponse HTTP. + + + +Ici, le temps passé en vert correspond au temps d'attente avant le chargement de la ressource, le temps passé en violet correspond au temps d'attente de chargement de la ressource (TTFB - Time To First Byte) et enfin le temps en gris correspond au temps de réception de la ressource. + +Voici à quoi ressemble le chargement des ressources sous le protocole HTTP/2 : + + + +Nous voyons clairement ici que le temps alloué à attendre les ressources (le temps en vert) a complètement disparu et que toutes les ressources sont bien chargées "en même temps", en utilisant le même flux. + +De plus, étant donné que les moteurs de recherche se basent de plus en plus sur le temps de chargement des pages pour améliorer leur référencement, le passage au protocole HTTP/2 est également un gros plus pour le `SEO`. + +Pour bien vous rendre compte de la vitesse de chargement des ressources, je vous propose la démo suivante : + +* Chargement avec `HTTP/1.1` : [http://http2.golang.org/gophertiles?latency=0](http://http2.golang.org/gophertiles?latency=0) +* Chargement avec `HTTP/2` : [https://http2.golang.org/gophertiles?latency=0](https://http2.golang.org/gophertiles?latency=0) + +## HPACK : Compression des headers + +Cette nouvelle version du protocole vient également avec une compression des headers envoyés par le serveur afin d'optimiser les flux échangés. + +Ainsi, si on effectue une première requête sur un site HTTP/2 et que nous récupérons les headers suivants : + +``` +:authority: vincent.composieux.fr +:method: GET +:path: / +:scheme: https +accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 +accept-encoding: gzip, deflate, sdch, br +``` + +Lors de ma prochaine requête, les headers `:authority` , `:method` , `:scheme` , `accept`  et `accept-encoding` ne vont potentiellement pas changer. +HTTP/2 effectuera alors une compression sur ceux-ci. + +Pour vous rendre compte de la compression des headers, je vous invite à utiliser l'outil [h2load](https://nghttp2.org/documentation/h2load-howto.html) permettant d'effectuer un benchmark des appels HTTP/2, en effectuant ici deux requêtes sur votre application : + +```bash +$ h2load https://vincent.composieux.fr -n 2 | grep traffic +traffic: 32.25KB (33023) total, 650B (650) headers (space savings 25.29%), 31.51KB (32270) data +``` + +On voit ici que mes headers m'ont permis d'`économiser 25,29%` d'échanges dans les headers. + +## Server Push (preload) + +Ceci est une petite révolution dans le chargement des ressources d'un navigateur. + +Votre serveur va en effet pouvoir pousser dans le cache de votre navigateur des ressources dont votre application aurait (ou pourrait avoir) besoin, et ce, uniquement lorsque vous détectez que le client en aura besoin. + +Afin de pré-charger une ressource (preload), il vous suffit d'envoyer un header HTTP de la forme suivante : + +``` +Link: ;rel="preload";as="font" +``` + +Vous pouvez bien sûr définir plusieurs headers `Link` , et les attributs `as` peuvent prendre les valeurs suivantes : `font` , `image` , `style`  ou `script`. + +Il est également possible d'utiliser le markup HTML pour précharger vos ressources : + +```html + +``` + + +Aussi, si vous utilisez le framework PHP [Symfony](https://www.symfony.com), notez que celui-ci a intégré le push d'assets dans sa version 3.3. Pour se faire, il vous suffit de spécifier dans Twig : + +{% raw %} +```html + +``` +{% endraw %} + +Pour plus d'informations, rendez-vous sur : [http://symfony.com/blog/new-in-symfony-3-3-asset-preloading-with-http-2-push](http://symfony.com/blog/new-in-symfony-3-3-asset-preloading-with-http-2-push) + +Notez également qu'un nouveau composant Symfony est à l'étude sur [cette Pull Request](https://github.com/symfony/symfony/pull/22273) afin de gérer tous les types de liens disponibles afin d'effectuer du pré-chargement ou du push (preload, preset, prerender, ...). + +## Server Hints (prefetch) + +Notez que cette méthode n'est pas liée à HTTP/2 car disponible bien avant mais il est toutefois intéressant d'aborder la différence entre ces deux méthodes `prefetch`  et `preload`. + +Si preload va en effet charger une ressource dans le `cache du navigateur`, prefetch lui, va s'assurer que le client ne l'a pas déjà à disposition et récupérera la ressource `uniquement si le client en a besoin`. + +Son fonctionnement est identique sauf que vous n'avez pas besoin de préciser l'attribut `as`: + +``` +Link: ; rel=prefetch +``` + +# Utiliser le protocole HTTP/2 + +Si vous utilisez nginx, le protocole HTTP/2 est supporté depuis la `version 1.9.5` et il vous suffit simplement de spécifier dans l'attribut `listen`  que vous souhaitez utiliser : "http2" +Exemple : + +```lua +server { + listen 443 ssl http2; + ... +``` + +Afin de vous assurer que HTTP/2 est bien activé sur votre serveur, je vous invite à taper `nginx -V`  afin de vous assurer que vous disposiez bien de l'option de compilation `--with-http_v2_module`  ainsi qu'à vérifier que votre version d'OpenSSL utilisée par nginx est récente. + +À partir de là, vous n'avez plus qu'à redémarrer votre serveur web afin de profiter du protocole. + +`Note` : Pour information, si le protocole http/2 n'est pas disponible dans le navigateur du client, le serveur web effectuera tout seul une fallback sur le protocole http/1.1. + +Côté Apache, les `versions 2.4.12` et supérieures supportent également le protocole. + +Globalement, l'activation du protocole HTTP/2 est assez simple. Si vous venez du monde Javascript, un package [http2](https://www.npmjs.com/package/http2) est également disponible afin d'instancier un serveur `express` avec la nouvelle version du protocole. + +# Conclusion + +HTTP/2 peut être dès maintenant utilisé sur vos applications web et ne peut faire que du bien à celles-ci sur plusieurs aspects (performances, SEO, chiffrement, ...). +Sa facilité de mise en place et son support sur un grand nombre de technologies est également un atout majeur qui devrait vous aider à passer le cap ! + +Et après ? Aucun travail n'est actuellement commencé au sujet d'HTTP/3 mais l'avenir et les technologies nous réserveront bien une troisième version de ce protocole tant utilisé ! diff --git a/_posts/2017-04-12-http2-future-present.md b/_posts/2017-04-12-http2-future-present.md new file mode 100644 index 000000000..0d0d4060a --- /dev/null +++ b/_posts/2017-04-12-http2-future-present.md @@ -0,0 +1,167 @@ +--- +layout: post +title: "HTTP/2 is not future. It's present." +permalink: /en/http2-future-present/ +author: vcomposieux +date: '2017-04-12 12:00:00 +0100' +date_gmt: '2017-04-12 12:00:00 +0100' +categories: + - Javascript + - Mobile + - Php +tags: + - compression + - header + - encryption + - protocol + - tls +--- +Remember, in `may 1996`, the very first HTTP protocol version (HTTP/1.0) was born. +This protocol is described as a [RFC 1945](https://tools.ietf.org/html/rfc1945). + +But time as passed and web applications has evolved a lot. We are now creating web applications that brings more and more logic in the browser and for that we need to load more and more assets: this means that we have to load multiple CSS which sometimes are making animations in the browser or also complex operations, more and more Javascript and images too. + +`HTTP/1.1` release has offered and allowed us to use a kind of new web technologies we've known these last years, but web application are more and more done on smartphones and other connected devices, which is why the needs are now focused on web browsing performances. + +After a first step made by Google in 2009 with the `SPDY` protocol, `HTTP/2` is finally going in the same direction with the [RFC 7540](https://tools.ietf.org/html/rfc7540). + + +# HTTP/2 Introduction + +Nowadays, HTTP/2 protocol is supported by most browsers and it's important to point out. While writing this blog post, only Opera Mini does not implement the new protocol, as shown on the following table: + + + +That being said, you can consider upgrading your own web applications to HTTP/2 as soon as possible and thus offer high browsing performances to your visitors. + +Indeed, HTTP/2 will bring to you new features which will help a lot on improving your applications. We will describe them in the rest of this article. + + +## TLS native support + +Even if encryption is` not mandatory`, most of browsers today only support HTTP/2 using TLS associated encryption and this is not really a problem because it's highly recommended to obtain a SSL certificate, which can be done easily and for free with services like Let's Encrypt. This will also help you to secure your application if they are not already. + +For information, if you choose to not use encryption, the protocol diminutive will be `h2c` , where it will be `h2`  if you do. + +If you want more information about how to [improve SSL exchanges security](https://vincent.composieux.fr/article/improve-ssl-exchanges-safety-made-by-your-web-server), I highly invite you to read my blog post on this topic. + + +## Stream Multiplexing + +HTTP/1 resources were loaded one by one as you can see below on a HTTP/1 application waterfall. HTTP/2 will allow to gain a lot of time on "waiting time" because multiple resources could be sent/downloaded by the client using the same HTTP stream (which is often called binary stream). + + + +Here, time passed and displayed in green color is corresponding to wait time before resource loading. Purple time is corresponding to resource loading time (TTFB - Time To First Byte) and finally the grey time is corresponding on the resource reception to the client. + +Here is a waterfall of resources loading using the HTTP/2 protocol: + + + +You can clearly see here that time allocated to wait on resources (old-green time) has disappeared completely and all resources are clearly loaded in the same time because they are in the same stream. + +Moreover, given that search engines are taking the page load time as an important metric for upgrading rank of websites, this is a great reason to go on HTTP/2 protocol: it will be a great improvement for your SEO. + +In order to help you to visualize the resource loading speed time, here is a demo comparing HTTP/1 and HTTP/2 made by Golang: + +* Chargement avec `HTTP/1.1` : [http://http2.golang.org/gophertiles?latency=0](http://http2.golang.org/gophertiles?latency=0) +* Chargement avec `HTTP/2` : [https://http2.golang.org/gophertiles?latency=0](https://http2.golang.org/gophertiles?latency=0) + +## HPACK: Headers compression + +This new protocol version also comes with headers compression that are sent by the server in order to optimize stream exchanges. + +This way, if I make a first request on a HTTP/2 website, I will retrieve the following headers: + +``` +:authority: vincent.composieux.fr +:method: GET +:path: / +:scheme: https +accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 +accept-encoding: gzip, deflate, sdch, br +``` + +On my next request, headers `:authority` , `:method` , `:scheme` , `accept`  and `accept-encoding`  will probably not change. +HTTP/2 will be able to compress them to gain some space in the response. + +In order to let you test header compression by yourself, I invite you to use the [h2load](https://nghttp2.org/documentation/h2load-howto.html) tool, which you can use to make a benchmark on HTTP/2 calls, by making here two requests: + +```bash +$ h2load https://vincent.composieux.fr -n 2 | grep traffic +traffic: 32.25KB (33023) total, 650B (650) headers (space savings 25.29%), 31.51KB (32270) data +We can see here that my compressed headers `saved me 25.29%` of exchange size. +``` + +## Server Push (preload) + +This is a little revolution on browser resource loading. + +Indeed, your web server will be able to push assets in your browser's cache that your application will need or oculd have needed, and this, only when you detect that your client will need that resource. + +In order to preload a resource, you just have to send a HTTP header formatted in the following way: + +``` +Link: ;rel="preload";as="font" +``` + +You can of course define multiple `Link` headers, and `as`  attributes can also take the following values: `font` , `image` , `style`  or `script`. + +It's also possible to use the HTML markup to preload your resources: + +``` + +``` + +Also, if you use the Symfony PHP framework, please note that this one has implemented assets preloading in version 3.3. To use it, you just have to use the preload wrapper this way: + +{% raw %} +``` + +``` +{% endraw %} + +For more information about this feature, you can visit: [http://symfony.com/blog/new-in-symfony-3-3-asset-preloading-with-http-2-push](http://symfony.com/blog/new-in-symfony-3-3-asset-preloading-with-http-2-push) + +Also note that a new Symfony component is currently in review [on this Pull Request](https://github.com/symfony/symfony/pull/22273) in order to manage all available links that allow to preload or push (preload, preset, prerender, ...). + +## Server Hints (prefetch) + +Please note that this method is not related to HTTP/2 because it's available for a long time but it's interesting to compare it with `preload`. + +So, preload will load a resource `into your browser's cache` but prefetch will ensure that the client doesn't already have this resource and will retrieve it `only if the client needs it`. + +Its use case is quite the same depending on you not having have to specify the `as` attribute: + +``` +Link: ; rel=prefetch +``` + +# Use the HTTP/2 protocol + +In the case of your web server being nginx, HTTP/2 protocol is supported since the `1.9.5 version` and you just have to specify in the `listen`  attribute that you want to use http2: + +``` +server { + listen 443 ssl http2; + ... +``` + +In order to ensure that HTTP/2 is fully enabled on your server, I invite you to type `nginx -V`  and check if you have the `--with-http_v2_module`  compilation option enabled. Additionally, you can also check that your OpenSSL version used by nginx is recent. + +Starting from here, you just have to restart your web server in order to enable the new protocol. + +`Note:` For your information if http/2 is not supported by the client's browser, your web server will automatically fallback on http/1.1 protocol. + +On Apache web server side, `versions 2.4.12` and greater also support HTTP/2. + +Globally, HTTP/2 protocol activation is quite simple. If you come from Javascript world, you have to know that a [http2](https://www.npmjs.com/package/http2) package is available in order to instanciate an `express` server with the new protocol version. + + +# Conclusion + +HTTP/2 can be used starting today on your web applications and can only be good (for your users mostly) but also for your application on multiple things: performances, SEO, encryption. + +It's also really easy to setup and support a large panel of languages which should really help you to go forward. + +So, what's next? No work has begun until now on a HTTP/3 or a new version after HTTP/2 but future and technologies should reserve us a third version of this highly used protocol! diff --git a/_posts/2017-05-09-retours-sur-la-dotscale-2017.md b/_posts/2017-05-09-retours-sur-la-dotscale-2017.md new file mode 100644 index 000000000..10a827f2d --- /dev/null +++ b/_posts/2017-05-09-retours-sur-la-dotscale-2017.md @@ -0,0 +1,194 @@ +--- +layout: post +title: "Retours sur la dotScale 2017" +permalink: /fr/retours-sur-la-dotscale-2017/ +author: vcomposieux +date: '2017-05-09 12:00:00 +0100' +date_gmt: '2017-05-09 12:00:00 +0100' +categories: + - Dev Ops +tags: + - conferences + - devops + - dot + - dotccale + - scalability +--- +Nous avons assisté à la 5ème édition de la dotScale (2017) qui se tenait cette année à Paris. Cette édition se déroulait dans le Théâtre de Paris et nous tenons à saluer les partenaires et volontaires pour l’organisation de cette conférence qui s’est très bien déroulée. + +# Introduction + +Beaucoup de monde dans ce théâtre et beaucoup de stands sponsors/partenaires variés entre outils de monitoring, Google Cloud Platform, outils publicitaires ou de réservation en ligne. + +Côté conférences, beaucoup de contenu autour du scaling d'applications, de bases de données, mais aussi certaines très intéressantes sur des sujets annexes comme la gestion de crise en production sur de grosses infrastructures. + +Si vous n’avez pas pu y participer, pas de panique, nous vous avons préparé un retour sur les talks ayant eu lieu lors de cette édition : + +## Benjamin Hindman - Co-créateur d'Apache Mesos et Co-fondateur de Mesosphere + +Benjamin nous décrit dans ce talk le fonctionnement d'Apache Mesos et de ce que sa société Mesosphere peut mettre en place. + +Il faut avant tout connaitre l'utilité d'Apache Mesos : il s'agit d'un outil permettant de gérer les ressources d'un datacenter (en tout cas plusieurs machines) afin de permettre l'exécution d'applications sur un système distribué. + +Ainsi, Apache Mesos gère les ressources dont vos applications ont besoin telles que le CPU, la RAM, les IO pour stockage de données, etc ... + +Mesosphere vient ensuite avec la couche OS s'appuyant sur Apache Mesos permettant de gérer au mieux vos applications et vos ressources. + +## Neha Narkhede - Co-créatrice d'Apache Kafka et CTO de Confluent + +Neha dans ce talk nous a présenté pourquoi et comment passer d'un système d'information en "spaghetti" (avec des échanges de flux entre toutes les applications, bases de données et autres) en un système d'information centralisé grâce à Apache Kafka. + +Apache Kafka va en effet permettre de centraliser tous les événements déclenchés par vos applications et de permettre aux applications ou encore bases de données ou outils variés de réagir au déclenchement de ceux-ci. Les échanges de votre SI seront ainsi beaucoup mieux organisés et surtout il vous sera plus simple de les suivre. + +L'exemple devient beaucoup plus parlant lorsque Neha s'appuie sur le cas de LinkedIN qui échange plus de 1 400 000 000 000 d'événements par jour. + +De plus, l'usage d'Apache Kafka devient de plus en plus simple grâce à son API d'interconnexion qui dispose aujourd'hui d'un grand nombre de connecteurs tels que Cassandra, Oracle, ElasticSearch, Mysql, MongoDB, Hadoop, ... + +*Les slides sont à disposition ici* : [https://speakerdeck.com/nehanarkhede/the-rise-of-real-time](https://speakerdeck.com/nehanarkhede/the-rise-of-real-time) + +## Adrian Cole - Lead de Zipkin + +Ce talk d'Adrian portait sur les 3 éléments clés permettant de surveiller vos applications : + +* Logger : le fait d'enregistrer les événements de vos applications, +* Ajouter des métriques : le fait de combiner des données afin d'obtenir une tendance, +* Tracer : pour identifier les causes entre les services interagissant avec votre application. + +Pour l'action de logger, il convient de bien structurer le format de vos logs afin de vous permettre de les exploiter au mieux. + +Les métriques, quant à elles, sont simplement un nombre indicateur apparaissant à un moment donné. Il est donc important d'avoir l'association de temps avec cette donnée. + +Enfin, il vous faudra passer un identifiant unique de requête entre toutes vos dispositions afin de tracer les échanges entre vos applications. + +*Les slides sont à disposition ici* : [https://speakerdeck.com/adriancole/observability-3-ways-logging-metrics-and-tracing](https://speakerdeck.com/adriancole/observability-3-ways-logging-metrics-and-tracing) + +## Ulf Adams - Lead de Bazel + +Bazel est le système de build utilisé par Google sur une petite partie de ses applications pour répondre à une problématique de temps de builds importants, sur certaines applications dont le code source était un unique repository monolithique. + +Ulf et son équipe planchent alors sur ce problème de temps de build exponentiel en travaillant en étroite collaboration avec les équipes de développement avec cet outil de build et d'exécution de tests nommé Bazel. + +Parfois même, il est nécessaire d'ajouter des portions de code spécifique permettant d'améliorer les temps de build. + +Le mot de la fin de ce talk était d'essayer de sortir le plus possible les librairies qui peuvent être indépendantes dans votre application afin de ne pas avoir à maintenir une base de code énorme. + +## Aish Raj Dahal - Ingénieur chez PagerDuty + +Ce talk n'est pas évident à retranscrire par écrit car il faut vraiment entendre l'histoire de Aish par soi-même afin de comprendre ce qu'il veut dire. + +En effet, ce talk traitait de la gestion de crise lors d'un incident majeur en production : garder son sang froid et considérer les éléments suivants : + +* Quelles sont les actions à prendre en compte ? +* Comment diviser les tâches dans l'équipe disponible à ce moment là ? Qui fait quoi ? +* Comment notifier en interne et communiquer au(x) client(s) ? +* Comment s'y préparer ? et surtout : apprendre de ses erreurs. + +Je vous encourage à regarder la vidéo du talk lorsqu'elle sera disponible sur le site des dotConferences car l'orateur était vraiment prenant ! + +*Les slides sont à disposition ici* : [https://speakerdeck.com/aishraj/chaos-management-during-a-major-incident](https://speakerdeck.com/aishraj/chaos-management-during-a-major-incident) + +## Mitchell Hashimoto - Fondateur Hashicorp + +Dans ce talk, Mitchell a mis l’accent sur l’aspect central du produit de la suite Hashicorp : Vault ([https://www.vaultproject.io](https://www.vaultproject.io)). + +En effet, dans l’organisation DevOps, ce produit permet de stocker des données de manière sécurisée pour toutes les équipes projet : il permet aussi bien de stocker des clés d’API (qui seront utiles aux développeurs) que des données de configuration réseau (qui dans ce cas sera utile pour les Ops). + +Il met à disposition plusieurs méthodes pour authentifier les accès aux ressources stockées dans Vault comme par exemple le Multi-Factor App ou encore via certificats TLS. + +Le stockage des données (encryptées bien sûr) peut ensuite être effectué sur le back-end de votre choix (MongoDB, PostgreSQL, AWS, Consul, ...). + +L’objectif principal de ce produit est de donner les moyens aux personnes de sécuriser leurs applications et infrastructures. + +*Les slides sont à disposition ici* : [https://speakerdeck.com/mitchellh/scaling-security](https://speakerdeck.com/mitchellh/scaling-security) + +Petit fait marquant pour l’occasion : Mitchell a profité de sa visite à Paris pour demander sa femme en mariage ([https://twitter.com/mitchellh/status/856202103194353664](https://twitter.com/mitchellh/status/856202103194353664)), tous nos vœux de bonheur ! + +## James Cammarata - Mainteneur principal d’Ansible + +James a commencé très fort son talk en pointant du doigt les erreurs d’ingénieurs qui ont eu pour effet de causer un début de black-out sur Internet ces dernières années. + +Comme exemple récent, nous retiendrons la coupure d’une région AWS S3 aux Etats-Unis récemment ([https://aws.amazon.com/fr/message/41926/](https://aws.amazon.com/fr/message/41926/)) dont l’erreur était clairement humaine. Un employé était en effet en train de débugger sur le système de paiement d’AWS et a voulu couper un serveur afin d’effectuer un test. Malheureusement, il y a eu un “effet domino” qui a causé l’arrêt de tous les serveurs AWS S3 de toute une région. + +Le talk s’est ensuite recentré sur Ansible et les moyens que nous avons à notre disposition afin d’éviter ce genre de problème. + +Par exemple, n’écrivez jamais ce genre de ligne : + +``` +- name: Removes backup directory. + shell: rm -rf /{{ backup_directory }} +``` + +Dans ce cas, si la variable "backup_directory" n’est pas définie, les données de votre disque seront effacées. Préférez l’utilisation du module “file” d’Ansible qui s’occupera, lui, de faire la vérification que la commande est cohérente. + +D’autres cas d’erreurs sont également possibles et ce sont souvent les variables qui en sont la cause. James invite donc à toujours préfixer les variables utilisées dans le code Ansible. + +Autre détail annexe mais “fun” de ce talk : James a développé un module Ansible permettant d’interagir avec l’API Phillips Hue. Si cela vous intéresse, il est disponible à cette URL : [https://github.com/jimi-c/hue](https://github.com/jimi-c/hue) + +Enfin, un point important évoqué lors de l’interview qui a suivie : Ansible Tower ([https://www.ansible.com/tower](https://www.ansible.com/tower)) devrait par la suite être disponible en open-source. + +## David Mazières - Chief Scientist chez Stellar et Professeur à l’université de Stanford + +Un talk très intéressant mais pas forcément évident à suivre pour tout le monde ! + +En effet, l’objectif de ce talk était de nous faire prendre conscience du protocole Consensus ([https://fr.wikipedia.org/wiki/Consensus_(informatique)](https://fr.wikipedia.org/wiki/Consensus_(informatique))) et donc d’un monde où tout le monde peut avoir le moyen de vérifier une information. + +Le premier exemple dont a parlé David, et qui semble évident, sont les autorités de certification. Aujourd’hui, lorsqu’elles délivrent un certificat, elles sont les seules habilitées à contrôler l’authenticité de celui-ci. + +Mais qu’adviendrait-il si elles étaient corrompues ? Si Apple fournissait des certificats à la NSA par exemple … + +Le protocole, largement utilisé par David -dans la société Stellar pour laquelle il travaille- et particulièrement la façon dont il est appliqué a été expliqué en détail dans ce talk. + +Plusieurs autorités sont ainsi capables d’authentifier une information. + +## Marco Slot - Ingénieur logiciel chez Citus Data + +Fort de son expérience chez Citus Data, Marco Slot nous fait part de la difficulté de scaler une base de données SQL, pourtant souvent centrale dans une application. Mais il nous rappelle aussi que difficile n’est pas impossible... + +Grâce au système ouvert de PostgreSQL et particulièrement à la mise à disposition d’API permettant de créer des extensions pouvant se brancher à plusieurs niveaux dans l’exécuteur SQL, Marco nous laisse comprendre qu’il est ainsi possible de créer une extension permettant de mettre en place du “sharding” en vue de récupérer des données (par exemple) à partir de plusieurs noeuds PostgreSQL lorsqu’une requête est effectuée. + +La conclusion de ce talk est sans appel : PostgreSQL, grâce à son système d’extension, ne se cantonne plus à la fonction de simple base de données, mais devient ainsi une plateforme SQL extensible, et surtout scalable. + +*Les slides sont à disposition ici* : [https://speakerdeck.com/marcocitus/scaling-out-postgre-sql](https://speakerdeck.com/marcocitus/scaling-out-postgre-sql) + +## Andrew Shafer - Directeur de technologie chez Pivotal +Ce talk est presque de nature philosophique. Pour autant, les propos avancés sont assez denses. + +Cette présentation sur le DevOps en général et les termes à la mode comme les “micro-services” nous démontraient qu’il n’est pas possible de pratiquer correctement ces méthodologies sans changer d’organisation et de méthodes de travail. + +En effet, voici une liste de quelques termes définissant ce que les gens souhaitent lorsqu’ils parlent de DevOps, selon Andrew : évolutivité, disponibilité, fiabilité, opérabilité, facilité d’utilisation, le tout gratuitement, et sans rien changer. + +Vous en conviendrez, les deux derniers termes sont bien sûr très compliqués à obtenir, et surtout, il ne sont pas possible sans toucher aux deux parties essentielles : le logiciel et l’humain. Les deux vont de paire pour réussir à adopter ces concepts. + +Il définit alors la méthodologie “Calms” pour arriver à pratiquer correctement ces concepts: “Culture, Automation, Lean, Metrics, Sharing”. + +*Les slides sont à disposition ici* : [https://www.slideshare.net/littleidea/the-end-of-the-beginning-devopsdays-denver-2017](https://www.slideshare.net/littleidea/the-end-of-the-beginning-devopsdays-denver-2017) + +## Clay Smith - Technologue chez New Relic + +Clay s’est amusé dans ce talk a rechercher le serveur dans le “serverless”. + +En effet, il s’est basé sur les Lambdas d’AWS afin de déterminer de quelles façons elles étaient exécutées. + +Ses trouvailles sont assez intéressantes : en exécutant un bout de code NodeJS, il a ainsi pu obtenir l’utilisateur courant sur le serveur et même plus : des informations provenant de `/proc`. + +Nous pouvons donc savoir que les serveurs sur lesquels tournent nos Lambdas (serverless) ont en fait les caractéristiques suivantes (équivalent à peu près à un serveur EC2 de type c4.large) : + +* Processeur : 2x Intel(R) Xeon(R) CPU E5-2666 v3 @ 2.90 Ghz +* Mémoire vive : 3857664 KB (soit environ 4 GO de ram) +* Interface réseau : 10 Gbps + +Chaque Amazon Lambda pouvant prendre de 128 MO à 1,5 GO de mémoire vive (et un temps d’exécution maximum de 5 minutes), cela vous laisse imaginer le nombre de machines nécessaires pour faire tourner vos lambdas. + +Clay montre également que le temps d’exécution varie entre la première lambda et les autres appelées en callback (lorsqu’elles sont déjà initialisées). Il y a donc un laps de temps alloué à l’instanciation des lambdas. + +Il conclut ensuite sur le fait qu'idéalement les lambdas sont à utiliser lorsque vous avez des traitements importants à effectuer, de façon occasionnelle en réponse à un événement bien défini qui n’est pas sensible à un temps de latence. + +*Les slides sont disponibles ici* : [https://speakerdeck.com/smithclay/searching-for-the-server-in-serverless](https://speakerdeck.com/smithclay/searching-for-the-server-in-serverless) + +À lire également, l'article sur comment Clay a pu monter un serveur SSH sur une lambda : [https://medium.com/clog/ssh-ing-into-your-aws-lambda-functions-c940cebf7646](https://medium.com/clog/ssh-ing-into-your-aws-lambda-functions-c940cebf7646) + +# Conclusion + +Nous avons passé une très bonne journée, intense en informations. Les conférences très techniques n'étaient pas forcément évidentes à suivre mais étaient pour autant très intéressantes, et si elles n'arrivent pas toujours à convaincre, elles ont au moins le mérite de faire réfléchir. + +À l'année prochaine ! diff --git a/img/authors/vcomposieux.jpg b/img/authors/vcomposieux.jpg new file mode 100644 index 0000000000000000000000000000000000000000..dbb00158ee1d2b0a7721861f8613c2355656b325 GIT binary patch literal 213403 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

1wvKI;M|2O)SrxlOGqjY>xHD1UwqgE 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 literal 0 HcmV?d00001