我为什么编写Net::Google::CivicInformation

在我成为一名计算机程序员之前,我曾尝试成为一名“人程序员”,以一名文字工作者的身份出现,正如我那时所称呼自己的。我曾担任过作家、编辑、翻译和记者等多种职业,但成功甚微,主要原因有两个。那时候,你需要一个出版商来发现你的作品足够有趣,值得出版,在任何人阅读之前,这确实是一个很高的门槛。其次,很难让人类对我的内容作出反应(我仍然感到困惑,为什么电影评论家在经过一个月的政治揭露之后,得到的读者来信比我多)。

我还记得1994年初的那个令人敬畏和启发的时刻,当时在Circuit City购买了一台闪亮的i286 PC后,我发现了一个万维网及其承诺的没有出版商和编辑约束的世界。我立刻投入其中,成为最早的“网站管理员”之一,这使我不得不学习Perl。在很短的时间内,我对构建发布内容的引擎的热情超过了创作内容。我仍然有门槛自由的进入点,至于我的第二个抱怨……嗯,我发现计算机对我的写作反应比人类更可预测。

快进近25年,很多东西都发生了变化,但我仍然会因为构建“网站”而感到兴奋。当然,现在它们被称为REST API或webapp或微服务或 whatever the newest term is,我主要是向大型组织内的其他计算机提供JSON数据包——自从我使用CGI.pm以来已经过去了20年——但我非常幸运,能够在同一个领域拥有一个长期职业生涯,这个领域技术快速演变,始终有Perl及其令人惊叹的贡献者社区的支持。

你可能会想,在花了一周时间在工作上构建API之后,我可能会感到满足,但上个周末我发现自己在渴望一个新的项目,一个工作之外的项目,但使用我的专业知识,并且对其他人有实际价值的项目。我还特别想看看我是否可以为CPAN提供一个与有用的公共API接口的库。多年来,我贡献了一些微不足道的分发,发现这非常令人满意,同时也迫使我在质量控制细节方面提高水平。然而,我从未找到过机会在我最擅长的领域贡献一个模块。

当然,所有最好的想法都已经有人采用了,尤其是在CPAN上。似乎有各种API都有对应的客户端,但最终我偶然发现了一个我认为超级酷的API,但在Perl中却没有得到支持!Google Civic Information API为任何美国地址的所有从国家元首到县税务收税官的选举官员提供了广泛的联系信息。

像你们很多人一样,我想,我对最近持续进行的对民主和民主参与的攻击仍然感到有些震惊,更不用说那么多当选的“领导者”似乎与人民隔绝了。我还考虑了COVID大流行——不仅仅是它可怕的损失和政府应对的粗鲁无能,还有它如何让我们彼此更加孤立,以及为什么在比如你无法轻松地带着社区团体去拥挤的县议会会议发表意见的时候,将人们聚集在一起的技术现在变得更加重要。但是沉默就是同意,如果我们选出的官员听不到我们的声音,他们就会继续做他们的事情。

考虑到这一切,我开始创建一个用于API的Perl客户端。第一步是获取一个认证令牌,这是谷歌免费提供给开发者的(每天查询有限制)。认证非常简单,所以我使用HTTP::Tiny,很快就在我的模块中创建了一个客户端,可以连接到API并查询。因为API提供的数据中只有一种是当选官员的联系方式,我创建了一个父类Net::Google::CivicInformation和一个子类Net::Google::CivicInformation::Representatives,处理与代表相关的代码。这将允许我或其他作者在未来为其他端点编写同级的子类。

以下是用于获取代表数据的子类的一部分

package Net::Google::CivicInformation::Representatives;

our $VERSION = '1.02';

use strict;
use warnings;
use v5.10;

use Carp 'croak';
use Function::Parameters;
use JSON::MaybeXS;
use Try::Tiny;
use Types::Common::String 'NonEmptyStr';
use URI;
use Moo;
use namespace::clean;

extends 'Net::Google::CivicInformation';

##
sub _build__api_url {'representatives'}

##
method representatives_for_address (NonEmptyStr $address) {
    my $uri = URI->new( $self->_api_url );
    $uri->query_form( address => $address, key => $self->api_key );

    my $call = $self->_client->get( $uri );
    my $response;

    ...

父类有一个api_url属性,其强制转换将根URL添加到子类构建器覆盖返回的值之前。注意使用Function::Parameters进行签名和Type::Tiny进行类型验证,这些都有助于减少子例程的模板。

创建客户端分发的绝大部分工作是在决定如何整理和重新格式化谷歌返回的庞大的JSON数据结构。结果被组织到Open Civic Data Divisions中,这是谷歌为其服务采用的国际标准。一个OCD分区的ID可以是像ocd-division/country:us这样的通用ID,也可以是像ocd-division/country:us/state:ny/place:new_york/council_district:36这样的特定ID。谷歌提供了数据集的过滤功能,但我选择使用一个高级端点,该端点返回单个特定地址的所有级别官员(尽管大多数情况下单独使用邮编也可以)。

经过几次尝试,我以简单的方式使它工作,作为模块的消费者,这让我感到满意。我记录了它将返回的表示代表的数组引用的散列引用。我编写了一些测试,打包了分发,并将其上传到PAUSE。

设置webservice

下一步是将新客户端投入使用,一个面向公众的Web应用程序似乎是显而易见的选择。多年来,我了解到大多数人仍然认为.com域名最有吸引力,而且一个可读的名称至关重要。我决定使用ContactMyReps作为名称,注册了contactmyreps.com域名,并将其指向我的服务器。在工作中,我使用Mojolicious,但我更喜欢Dancer2——它对我来说感觉更轻量级、更灵活,也更符合Perl风格。

查找查询的POST路由处理程序

post '/find-by-address' => sub {
    my $params = params;

    if ( not $params->{address} ) {
        send_error('Error: address is required.', 400 );
    }

    my $client = Net::Google::CivicInformation::Representatives->new;

    my %result = ( address => $params->{address} );

    my $response = $client->representatives_for_address($params->{address});

    if ( $response->{error} ) {
        $result{error} = $response->{error};
    }
    else {
        $result{officials} = decode_utf8(encode_json($response->{officials}));
    }

    return template 'find-by-address', \%result;
};

Web应用程序的后端部分在一小时内就完成了,然后我开始处理展示。虽然花了一些时间,但我提出了一种既实用又美观的设计。在本地测试后,我准备部署,创建新的TLS证书并更新Apache配置后,网站就上线并运行了。(源代码可在GitHub上找到)。

我将链接发送给了几个朋友,看看他们的看法,并在 blogs.perl.org 上发布以获取一些实时测试,当其他人开始使用该网站时,我感到非常高兴。

总结

在不到24小时内构思和实施了这个想法,我感到非常兴奋,并且进展顺利。我认为,如果一件事情值得做,就值得做好,所以我注册了数百美元的谷歌广告来推广网站,使其在相关搜索结果旁边显示。我还设置了一个“给我买杯咖啡”的账户,并在搜索结果显示中放置了一个按钮。点击网站大约需要1.20美元,所以我估计如果10%的访客贡献一些东西,他们就会支付广告费用以触及新的受众,这样东西就能自我维持。

总的来说,这是一个度过冬季周末的好方法。如果您想查看,服务在 https://contactmyreps.com 上在线提供!

标签

尼克·托金

自1995年以来一直站在巨人肩膀上的经验丰富的Perl程序员;丈夫、父亲、驴农、一般业余爱好者。

浏览他们的文章

反馈

这篇文章有什么问题吗?请在 GitHub 上打开一个问题或拉取请求来帮助我们。