Упаковка веб-сервисов с помощью Moo

Недавно я взял на себя обслуживание клиента Perl, который упаковывает веб-службу доставки. В исходном состоянии проекта используются объекты Moo, которые отображаются непосредственно на объект веб-службы, например, есть классы Parcel, Address и Label.

В v2 API вы должны отправить все данные, которые представляют один из этих объектов, в веб-службу, после чего вы получите уникальный идентификатор для этого объекта, который должен использоваться для всех последующих транзакций.

Например, если я написал:

{ name   => 'Hunter',
  street => '121 Baker St',
  city   => 'New York',
  state  => 'NY',
}

Я бы вернул те же данные, но с включенным идентификатором:

{ id     => 'adr_xq1411',
  name   => 'Hunter',
  street => '121 Baker St',
  city   => 'New York',
  state  => 'NY',
}

У меня проблемы с решением, как создать эти объекты. В настоящее время у меня есть этот конструктор, который выполняет фактическую POSTing для получения идентификатора, а затем изменяет текущий объект:

sub BUILD {
    my $self = shift;

    my $requestor = Net::Easypost::Request->new;
    my $resp = $requestor->post( 
        '/addresses', 
        $self->serialize( [qw(street1 street2 city state zip)] ) 
    );

    # save the id for this Address from Easypost   
    $self->id( $resp->{id} );

    return $self;
}

Это распространенный подход при переносе веб-сервисов в Perl? Кажется, что идеальным методом было бы отправить POST в веб-сервис и создать все атрибуты объекта Address одновременно, но в Moo(se), когда вы находитесь в методе BUILD, объект уже создан.

Я не настолько знаком с идиомами для упаковки веб-сервисов, есть ли более простой способ, чем этот?

Любые советы / комментарии / предложения будут оценены.

1 ответ

Решение

Это, безусловно, один из способов сделать это. Это может быть хорошей идеей, чтобы выделить BUILD метод в роль. Что-то вроде:

package MyApp::PostOnBuild;

use Moo::Role;

has id          => (is => 'rwp');
has endpoint    => (is => 'ro', default => sub { '/addresses' });
has requestor   => (is => 'ro', default => sub { Net::Easypost::Request->new });
has field_names => (is => 'ro', builder => 1);

requires '_build_field_names';
requires 'serialize';  # or maybe just implement serialize within this role!

sub BUILD { }
after BUILD => sub {
    my $self = shift;
    my $resp = $self->requestor->post($self->endpoint, $self->serialize($self->field_names));
    $self->_set_id( $resp->{id} );
};

Теперь вам не нужно определять свои классы BUILD метод. Все, что им нужно сделать, это:

package MyApp::Address;

use Moo;
with 'MyApp::PostOnBuild';

my @fields = qw/ street1 street2 city state zip /;

has $_ => (is => 'ro') for @fields;

sub _build_field_names { \@fields }

sub serialize { ... }  # would this method be better defined in MyApp::PostOnBuild??

Обратите внимание, что requestor теперь является атрибутом, поэтому, когда вы тестируете класс, вы можете сделать что-то вроде:

my $adr = MyApp::Address->new(
    street1   => '123 Example Lane',
    city      => 'Sydney',
    state     => 'NSW',
    zip       => '2035',
    requestor => Test::Requestor->new,
);
Другие вопросы по тегам