Espiral: estrutura PHP / Go de alto desempenho



Olá, Habr. Meu nome é Anton Titov, CTO da Spiral Scout. Hoje eu gostaria de falar sobre o nosso elefante PHP. Ou melhor, sobre a segunda versão da estrutura PHP / Go de pilha cheia de código aberto - Spiral .

O Spiral é uma estrutura de componentes completos desenvolvida por nossa empresa há mais de onze anos e atende a uma centena de projetos reais. O pacote de software é baseado em muitas bibliotecas abertas e proprietárias, incluindo RoadRunner e Cycle ORM .

A estrutura é compatível com a maioria das recomendações de PSR, suporta MVC e executa de 5 a 10 vezes mais rápido que o Laravel / Symfony.

Se você nunca ouviu falar do Spiral e está se perguntando qual é a estrutura do PHP / Go e para onde foi a primeira versão, então seja bem-vindo ao gato.

Sobre a estrutura


O desenvolvimento da espiral começou em 2008/09 como um kernel portátil para aplicativos freelancers. Em 2010, finalmente formamos uma empresa de terceirização e, desde então, estamos empenhados em melhorar nossa pilha.

Como resultado, a estrutura foi transformada em um conjunto de componentes independentes, unidos por uma camada de integração comum.

O único anúncio público da primeira versão ocorreu no Reddit em 2017 e deixou claro para nós que, tecnicamente, a Spiral realmente não venceu os concorrentes naquele momento. Levamos em conta o feedback que recebemos, a experiência de projetos mais complexos e concluímos a segunda versão em maio de 2019.

Após um ano de documentação e execução de projetos reais, estamos prontos para enviar esse desenvolvimento ao público.

A principal diferença entre o Spiral 2.0 da geração anterior da estrutura e, talvez, de todas as outras estruturas PHP existentes é o servidor de aplicativos integrado RoadRunner, bem como a adaptação da arquitetura para um modelo de execução de longa duração (modo daemon).

Hybrid Rantheim


O principal conceito da estrutura é a simbiose entre o servidor de aplicativos escrito em Golang e o núcleo do PHP. O código do aplicativo PHP é carregado apenas uma vez na memória - dessa forma, você obtém uma economia significativa de recursos, mas perde a capacidade de executar o WordPress.



Você pode ler mais sobre o modelo híbrido aqui e aqui .

O servidor é responsável por toda a parte da infraestrutura: HTTP / FastCGI, comunicação com os agentes da fila, GRPC, WebSockets, Pub / Sub, cache, métricas, etc.

Escrever código para o Spiral praticamente não difere do código de qualquer outra estrutura. No entanto, os princípios do SOLID terão que ser mais responsáveis. Se o armazenamento em cache de dados do usuário anteriormente em singleton causou apenas graves problemas na revisão do código, agora esse código simplesmente não funcionará corretamente.

O conhecimento de Golang não é necessário para trabalhar com a plataforma. No entanto, depois de aprender um segundo idioma, você pode criar integrações quase perfeitas com as bibliotecas Golang. Por exemplo, incorpore um mecanismo de pesquisa de texto completo ou escreva seu próprio driver Kafka.

Atirar na perna era um pouco mais difícil, decidiu-se abandonar o sistema de eventos e ganchos e priorizar o trabalho com a pilha de chamadas de forma explícita.

A estrutura fornece um conjunto de ferramentas, como fechamentos de IoC, middleware, spoilers da camada de domínio e serviços imutáveis.

$container->runScope(
    [UserContext::class => $user],
    function () use ($container) {
        dump($container->get(UserContext::class);
    }
);

Embora essa abordagem imponha uma sobrecarga adicional, a capacidade de não descarregar o núcleo do programa, as conexões com o banco de dados e os soquetes da memória mais do que cobre essas limitações.

atuação


Na classe de pilha cheia de estruturas PHP, a Spiral compete principalmente com montagens baseadas em Swoole e com várias micro-estruturas.



Benchmarks completos estão disponíveis aqui e aqui .

Para nós, o desempenho é um efeito colateral da arquitetura escolhida. Temos certeza de que, com a configuração adequada e a substituição do PSR-7 por uma abstração mais leve, a produtividade pode ser aumentada em 50-80% (isso é comprovado pelo exemplo do ubiquity-roadrunner ).

Swoole. Swoole RoadRunner, PHP ++. , Go. , .

RoadRunner ( ), , Windows.

Spiral Swoole, !

PSR-*


A maioria dos componentes do Spiral é opcional para a sua compilação, a diferença entre compilação micro e completa está apenas no conteúdo composer.json. Se necessário, você pode usar as interfaces para substituir as bibliotecas padrão por implementações alternativas.

A camada HTTP da estrutura é escrita levando em consideração os padrões PSR-7/15/17, você pode alterar com segurança o roteador , a implementação de mensagens, etc.

A maioria das bibliotecas da estrutura pode ser usada fora da estrutura. Por exemplo, o RoadRunner funciona muito bem com o Symfony e o Laravel , e o Cycle ORM estará disponível no Yii3.

Componentes do servidor


Além dos componentes PHP, o assembly RoadRunner inclui várias bibliotecas escritas em Golang. A maioria dos serviços de servidor pode ser gerenciada no PHP.

Em particular, há um componente de fila que suporta o trabalho com os intermediários AMQP, Amazon SQS e Beanstalk. A biblioteca pode parar, reconectar e distribuir corretamente qualquer número de filas de entrada para vários trabalhadores.

Fora da caixa, há monitoramento no Prometheus e nos pontos de verificação de integridade, uma reinicialização a quente e um limite de memória. Para projetos distribuídos, há um servidor e cliente GRPC. Existem soquetes da Web, eles podem ser autorizados a partir de um aplicativo PHP e conectados ao barramento pub-sub (na memória ou no Redis). Os drivers de valor-chave estão sendo testados no momento.

INFO[0154] 10.42.5.55:51990 Ok {2.28ms} /images.Service/GetFiles
INFO[0155] 10.42.3.95:50926 Ok {11.3ms} /images.Service/GetFiles
INFO[0156] 10.42.5.55:52068 Ok {3.60ms} /images.Service/GetFiles
INFO[0158] 10.42.5.55:52612 Ok {2.30ms} /images.Service/GetFiles
INFO[0166] 10.42.5.55:52892 Ok {2.23ms} /images.Service/GetFiles
INFO[0167] 10.42.3.95:49938 Ok {2.37ms} /images.Service/GetFiles
INFO[0169] 10.42.5.55:52988 Ok {2.22ms} /images.Service/GetFiles




Portabilidade


A estrutura não requer PHP-FPM e NGINX. E todos os componentes Golang possuem drivers para trabalhar sem dependências externas. Assim, você pode usar filas, websockets, métricas sem instalar corretores ou programas externos.

./spiral serve -v -d

A Spiral não importa se você escreve um aplicativo distribuído enorme ou um site pequeno que envia "escreva para nós" em segundo plano. De qualquer forma, você pode usar as mesmas ferramentas, unificando o comportamento dos ambientes local e de produção.

Como a camada HTTP é opcional, é possível gravar aplicativos de console que processam dados em segundo plano usando um pacote de filas. Usamos esses programas para migração de dados.

Ciclo ORM


Como um ORM pronto para uso, vem o Ciclo ORM . Esse mecanismo do Data Mapper é muito semelhante em função ao Doctrine, mas muito diferente da arquitetura.

Como o Doctrine, o Cycle pode trabalhar com modelos de domínio puro, gerando migrações e organizando chaves estrangeiras de forma independente. O esquema de mapeamento pode ser descrito por código ou coletado de anotações. Mas, em vez do DQL, os Query Builders clássicos são usados.

//    
//        
$users = $orm->getRepository(User::class)
    ->select()
    ->where('active', true)
    ->load('orders', [
        'method' => Select::SINGLE_QUERY, // force LEFT JOIN
        'load'   => function($query) {
            $query->where('paid', true)->orderBy('timeCreated', 'DESC');
        }
    ])
    ->fetchAll();

$transaction = new Transaction($orm);

foreach($users as $user) {
    $transaction->persist($user);
}

$transaction->run();

O ciclo é executado mais rápido que o Doctrine nas seleções, mas mais lento ao persistir. O mecanismo suporta consultas complexas com várias estratégias de carregamento, classes de proxy, incorporação e fornece transações portáteis em vez do EntityManager global.

Mais detalhes podem ser ouvidos no PHP Russia 2020 .

O principal "recurso" do ORM é a capacidade de alterar o mapeamento de dados e links em tempo de execução. Em palavras simples, você pode permitir que os usuários definam independentemente o esquema de dados (o DBAL suporta introspecção e declaração de esquemas de banco de dados).

Você pode ler mais sobre como comparar Cycle, Eloquent e Doctrine 2 aqui .

Prototipagem Rápida


O Spiral inclui várias ferramentas para acelerar e simplificar o desenvolvimento. Os principais são injeção de dependência automática, configuração automática e pesquisa automática de modelos usando análise estática. Os comandos do console permitem gerar a maioria das classes necessárias e são facilmente personalizáveis.

Para integração no IDE, existe um sistema de prototipagem rápida. Usando o PrototypeTrait mágico, você pode obter acesso rápido a dicas no IDE.



A característica localiza automaticamente repositórios ORM, componentes padrão e pode indexar seus serviços por anotações. Sob o capô, o método magic __get é usado , o que garante uma rápida revisão do código.

Basta executar o comando `php app.php prototype: inject -r`, e o sistema de prototipagem removerá automaticamente toda a magia:

namespace App\Controller;

use App\Database\Repository\UserRepository;
use Spiral\Views\ViewsInterface;

class HomeController
{
    /** @var ViewsInterface */
    private $views;

    /** @var UserRepository */
    private $users;

    /**
     * @param ViewsInterface $views
     * @param UserRepository $users
     */
    public function __construct(ViewsInterface $views, UserRepository $users)
    {
        $this->users = $users;
        $this->views = $views;
    }

    public function index()
    {
        return $this->views->render('profile', [
            'user' => $this->users->findByName('Antony')
        ]);
    }
}

Sob o capô, o PHP-Parser é usado .

Segurança


Como a maioria de nossos aplicativos é desenvolvida para o segmento B2B, os assuntos de segurança precisam ser levados a sério.

Os componentes para validar solicitações complexas (filtros de solicitação), CSRF e criptografia (com base em criptografia defuse / php ) estarão disponíveis para você . O trabalho com cookies e a sessão oferece suporte à violação e à assinatura de dados no lado do servidor.

A estrutura fornece um componente de autenticação baseado em token expirado. Como driver, você pode usar sessões, um banco de dados ou mesmo JWT puro.

Ou todos os tipos de tokens ao mesmo tempo, se o requisito “conectar urgentemente SAML / SSO / 2FA!” Repentinamente voa para você :(

A autorização de acesso é realizada através do componente RBAC com algumas melhorias que permitem a operação no modo DAC e ABAC. Há suporte para muitas funções, anotações para proteger os métodos do controlador e um sistema de regras.

O trabalho com a camada de domínio ocorre através de uma camada interceptora intermediária . Assim, você pode criar restrições especiais em um grupo de controladores, pré-validar dados e quebrar erros.

Mecanismo de modelo


Se você gosta apenas do Twig - pode percorrer esta seção, basta instalar a extensão e usar ferramentas familiares. :)

Pronto para uso, vem o mecanismo de modelo Stemper, ou melhor, uma biblioteca para criar sua própria marcação DSL. Em particular, há um lexer completo, várias gramáticas, um analisador e acesso ao AST (semelhante ao PHP-Parser da Nikita).

É possível analisar várias gramáticas aninhadas. Assim, por exemplo, você pode usar as diretivas Laravel Blade e sua própria marcação DSL (na forma de tags HTML) no mesmo modelo. Acontece algo como componentes da web no lado do servidor.

Usamos esse componente para descrever interfaces complexas usando regras e primitivas simples.

<extends:admin.layout.tabs title="User Information"/>
<use:bundle path="admin/bundle"/>

<ui:tab id="info" title="Information">
  User, {{ $user->name }}
</ui:tab>

<ui:tab id="data" title="User Settings">
  <grid:table for={{ $user->settings }}>
    <grid:cell title="Key">{{ $key }}</grid:cell>
    <grid:cell title="Value">{{ $value }}</grid:cell>
  </grid:table>
</ui:tab>

Ele suporta escape automático com suporte ao contexto (por exemplo, a saída do PHP dentro do bloco JS converte automaticamente os dados em JSON), mapas de origem para trabalhar com erros. Os modelos são compilados no código PHP otimizado e fornecidos diretamente da memória do aplicativo.
O Stemper pode trabalhar totalmente com o DOM do documento (embora mais lento se você usar ferramentas especializadas).

Desenvolvimento de Framework


Obviamente, em mais de uma década de desenvolvimento dessa ferramenta, cometemos muitos erros. Alguns deles foram corrigidos para o lançamento da segunda versão, enquanto a outra parte requer uma revisão do conceito de alguns componentes.

Também não negamos que algumas coisas exijam um arquivo. E algumas perguntas podem soar para nós pela primeira vez. Embora a documentação tente cobrir o máximo de componentes, ela ainda pode ceder em alguns lugares.

Estamos abertos a críticas e sugestões, já que nós mesmos estamos usando ativamente essa ferramenta. O enorme estoque de melhorias está sendo resolvido lentamente pela nossa equipe e pela comunidade.

Muito trabalho será necessário para melhorar e acelerar o Ciclo, além de reescrever os Filtros de Solicitação de acordo com as RFCs mais recentes. Em desenvolvimento, há um componente para trabalhar e emular bancos de dados de Valor-Chave.

Simplesmente não conseguimos traduzir muitas coisas da primeira versão. Os planos são restaurar pacotes ODM, painel de administração, escrever um bom perfilador, etc.

Comunidade e Links


Você pode fazer login na nossa pequena comunidade Discord . Canal de telegrama .


Todo o código é distribuído sob a licença MIT e não possui restrições para uso comercial.

Obrigado pela atenção. Espero que nossas ferramentas sejam úteis em seus projetos!

All Articles