BT

最新技術を追い求めるデベロッパのための情報コミュニティ

寄稿

Topics

地域を選ぶ

InfoQ ホームページ アーティクル プログラミングの再考: クラウド時代のアプリケーション開発者のための言語とプラットフォーム

プログラミングの再考: クラウド時代のアプリケーション開発者のための言語とプラットフォーム

キーポイント

  • The modern programming world is split between highly trained professionals who work on low-level code and people who don’t have a deep background but focus on high-level app development that helps meet business requirements. 
  • Ballerina is an open source programming language, which focuses on improving the productivity of the latter group by providing necessary abstractions, tools, and platforms to create cloud native applications.  
  • Ballerina treats the network differently by making networking abstractions like client objects, services, resource functions, and listeners a part of the language’s type system. So developers can use the language-provided types to write network programs that just work.
  • Ballerina's network and data-friendly type system enables developers to model network communication patterns in an intuitive way that enables maximum productivity.
  • Built-in cloud support helps to generate corresponding deployment artifacts by reading defined annotation in source code. These artifacts can be Dockerfiles, Docker images, Kubernetes YAML files, or serverless functions.
  • Ballerina’s abstractions and syntax for concurrency and network interaction have been designed so that there is a close correspondence with sequence diagrams.

原文(投稿日:2020/05/15)へのリンク

今年初め、O’Reilly Mediaのコンテンツ戦略担当VPであるMike Loukides氏は、“Rethinking programming” という素晴らしい記事を発表した。この記事では、高度な訓練を受けた専門家と、深いバックグラウンドはないがビジネス要求を満たすものを作る経験が豊富な人との間で、プログラミングの世界がどのように分かれているかを調べている。

前者のグループはツールやフレームワーク、プラットフォームの構築にたずさわり、後者のグループは各種システムを統合してビジネスのためのアプリケーションを作ることに注力する。

汎用プログラミング言語はほぼ全て、高度な訓練を受けた専門家がローレベルのプログラミング問題を解決するための抽象化と構成要素により設計されている。

Ballerinaというオープンソースのプログラミング言語は、非常にアジャイルなプロセスを望み、ビジネス要求に対してアプリケーションをスケールさせるためにクラウドプラットフォームを用いる専門家を支援するため、新しい基本的な抽象化を導入している。

言語におけるネットワーク

何十年もの間、プログラミング言語はネットワークを単なるI/Oソースとして扱ってきた。そのため単純なAPIを公開するのに、開発者はシグナルが得られるまでネットワーク要求を待ち受ける明示的なループを書くことで、サービスを実装する必要があった。Ballerinaでは、クライアントオブジェクト、サービス、リソースファンクション、リスナーなどのネットワーク抽象化を言語の型システムの一部とすることで、これまでとは異なる形でネットワークを扱っている。これにより、言語が提供する型を使ってネットワークプログラムを書き、動かすことができる。

Ballerinaのservice型とlistenerオブジェクトを使用すると、単にresource function内にAPI主導のビジネスロジックを書くだけで、開発者はAPIを公開することができる。listenerオブジェクトに定義されたプロトコルに応じて、HTTP/HTTPSHTTP2gRPCWebSocketsとしてサービスを公開することができる。Ballerinaサービスはビルトインの同時実行機能を備えている。リソースメソッドへのリクエストは全て別のstrand(Ballerinaの同時実行単位)で処理され、サービスに暗黙の同時実行動作を与える。

以下のコードはBallerinaのservice型の構文を示している。

import ballerina/http;
service serviceName on new http:Listener(8080) {
   resource function newResource(http:Caller caller, http:Request request) {
       // API-led logic
   }
}

リクエスト/レスポンスのパラダイムでは、ネットワーク通信は呼び出しをブロックすることで行われるが、ネットワーク呼び出しに対してスレッドをブロックするのは非常にコストが高い。多くの言語フレームワークは非同期呼び出しをサポートしているが、開発者はコールバックベースのテクニックを用いてasync/awaitを実装する必要がある。以下のBallerinaコードはシンプルなHTTP GET操作を示している。一見するとブロッキング操作のように見えるが、内部的にはノンブロッキングI/Oを使用して非同期実行をしており、現在の実行スレッドをOSに開放して他の人が使用できるようになっている。

http:Client clientEndpoint = new("http://example.com");

public function main() {
   var response = clientEndpoint->get("/get?test=123");
   ----- 	
}

ネットワーク動作は信頼性が低いため、APIのリモート実行は障害に対して脆弱だ。リカバリには自動リトライが役に立つことがある。サービス提供を中断させないためにフェイルオーバーが役に立つこともあるし、複数のプログラムにわたる壊滅的なカスケード障害を防ぐためにサーキットブレーカーのような手法が役に立つこともある。Ballerinaのクライアントオブジェクトにはこうしたテクニックが備わっており、開発者がリモートネットワーク呼び出しを含んだ復元力があり堅牢なコードを書くのに役に立つ。

以下のコードは、Ballerina HTTPクライアントオブジェクトにおいて、ネットワーク関連エラーをハンドルするサーキットブレーカーの設定方法を示している。

http:Client backendClientEP = new("http://example.com", {
       circuitBreaker: {
           rollingWindow: {
               timeWindowInMillis: 10000,
               bucketSizeInMillis: 2000,
               requestVolumeThreshold: 0
           },
           failureThreshold: 0.2,
           resetTimeInMillis: 10000,
           statusCodes: [400, 404, 500]
       },
       timeoutInMillis: 2000
   });

ネットワークサービスでは、各種ユーザー入力データを処理する必要がある。一般的に、全てのユーザー入力は適切にチェックしないと危険なものになってしまう。Taint分析は、ユーザー入力により変更される可能性のある変数を防ぐことで、セキュリティを高めるように設計されている。Ballerinaのビルトインtaint analyzerは、汚染されたデータがプログラム内をどう伝播するか観察することで、信頼されていない(汚染された)データを特定するのに役立つ。信頼されていないデータがセキュリティ上重要なパラメータに渡されると、コンパイラのエラーが発生する。汚染チェックはコンパイラの段階で実行されるため、プログラマは危険な入力の周囲に安全な壁を作るように、プログラムを再設計することができる。

ネットワークとデータフレンドリーな型システム

Ballerinaは構造的な型システムをサポートしており、シェイプ(shape)という値の抽象化で動作する。シェイプは値のストレージ同一性を基本的に無視する。つまり、値を他の値と比較する際、値の名前参照を考慮しない。これは独立に設計された複数のシステムからやってきたデータを組み合わせる時に、特に便利だ。

Ballerinaは、値の集合が、そのコンポーネント型の値空間の結合であるUnion型もサポートする。例えば、union型の変数を使ってstringもしくはintを格納できるが、値の型は常に一つだけある。

Ballerinaには、JSONやXMLといったビルトインのネットワークデータ型サポートがある。Ballerinaのjson型は、JSON形式のデータ表現を処理するために設計されている。これはビルトインのunion名で、次のように定義されている。

type json = () | boolean | int | float | decimal | string | json[] | map<json>

デフォルトで、Ballerinaにはopen recordがある。人物の詳細情報を表現するrecord型を見てみよう。

type Gender "male"|"female";

type Address record {|
   string city;
   string country;
|};

type Person record {
   string name;
   int birthYear; 
   Gender gender;
   Address address?;
};

ここでPerson型はopen record型であり、 “{” および “}” 区切り文字によるinclusive-record-type-descriptorで定義されている。Address型はclosed record型であり、“{|” および “|}” 区切り文字によるexclusive-record-type-descriptorで定義されている。また、Person recordのaddressは値を設定せずにスキップできるオプショナルフィールドで、“?”というサフィックスがついている。

新しい型Studentを作成しよう。

type Student record {
   string name;
   int birthYear; 
   Gender gender;
   Address address?;
   string college;
};

Student型はPerson型のサブタイプだ。Person型と比べて、string型のcollegeというフィールドが追加されている。これが可能なのは、Person型がopen record型であり、そのシェイプはcollegeというstringフィールドを持てるためだ。

public function main() {

   Student s1 = { name: "John", birthYear: 1995, gender: "male",
                 	college: "US Berkeley" };
   Student s2 = { name: "Kamala", birthYear: 1994, gender: "female",
                 address: { city: "Milpitas", state: "CA" ,country: "USA"}, 
college: "Stanford" };
   Person p1 = s1;
   Person p2 = s2;

   io:println("P1's details:" ,p1);
   io:println("P2's details:" ,p2);

}

$ ballerina run person.bal
Compiling source
    person.bal
Running executables

P1's details:name=John birthYear=1995 gender=male college=US Berkeley
P2's details:name=Kamala birthYear=1994 gender=female address=city=Milpitas state=CA country=USA college=Stanford

言語統合クエリは、複数のデータソースに対して単一の構文を使うことができる機能だ。これは複雑な問題を一連の短く理解しやすいクエリに分解するのに役立つ。

Ballerinaのクエリ式は、SQLライクな構文を用いた言語統合クエリ機能だ。SQL文とは異なり、クエリ式は型の安全性のおかげで設計時のミスを特定するのに役立つ。

import ballerina/io;

type Student record {
   string name;
   int age;
   string school;
};

type Result record {
   string name;
   string college;
   float gpa;
   string school;
};

public function main() {
   map<float> gpa = {"John": 4.1, "Bill": 3.9, "Sam": 3.3, "Jennifer": 3.1};
   Student s1 = {name: "John", age: 18, school: "Milpitas High School"};
   Student s2 = {name: "Bill", age: 17, school: "San Jose High School"};
   Student s3 = {name: "Sam", age: 18, school: "Clara Coutinho High School"};
   Student s4 = {name: "Jennifer", age: 18, school: "Fremont Union High School"};
   Student[] student = [];
   student.push(s1);
   student.push(s2);
   student.push(s3);
   student.push(s4);

   Result[] stanford = from var candidate in student
                       let float cgpa = (gpa[candidate.name] ?: 0),
                       string targetCollege = "Stanford"
                       where cgpa > 3.8
                           select {
                               name: candidate.name,
                               college: targetCollege,
                               gpa: cgpa,
                               school: candidate.school
                           };   

   io:println(stanford);
}

$ ballerina run query_expression.bal
Compiling source
    query_expression.bal
Running executables

name=John college=Stanford GPA=4.1 school=Milpitas High School name=Bill college=Stanford GPA=3.9 school=San Jose High School

from節はforeach文と似たような働きをする。イテレート可能な値からイテレータを作成し、イテレータにより返された値に変数をバインドする。letは変数をバインドする。where節は、from節によってバインドされた変数を参照できる条件付き実行手段を提供する。where節の条件がfalseと評価された場合、イテレータは続く節をスキップする。select節はイテレーションごとに評価される。このサンプルのクエリ式の結果は、select節の結果をメンバーとするlistになる。

ビルトインのクラウド技術統合

以前は、開発者はプログラムを書き、ビルドし、実行するだけだった。しかし、今は実行するのにも様々な方法がある。ベアメタルマシンや仮想マシンで実行することもできるし、コンテナとしてパッケージ化してKubernatesやサービスメッシュのようなプラットフォームにデプロイするプログラムもあれば、サーバーレスプログラムとして実行することもできる。しかし、こうしたデプロイの選択肢は開発者のプログラミング体験の一部ではない。開発者は与えられた実行環境でうまく動かすために、所定の方法でコードを書く必要があり、プログラミングの問題からこれを除外するのは適切ではない。

Dockerはアプリケーションとその依存関係をバイナリイメージにパッケージ化して、様々な場所(オンプレミスでも、パブリッククラウドでも、プライベートクラウドでも)で動かせるようにするのに役立つ。最適化されたイメージを作成するために、開発者は一連のベストプラクティスに従う必要がある。さもないと、構築したイメージのサイズは大きくなり、安全性は低くなり、多数の欠点を持つことになる。

Ballerinaコンパイラはアプリケーションのソースコードから最適化されたDockerイメージを作成することができる。service@docker:Config {}を追加することで、DockerfileとDockerイメージが生成される。

import ballerina/http;
import ballerina/docker;

@docker:Config {
   name: "hello",
   tag: "v1.0"
}
service Hello on new http:Listener(8080) {
   resource function hi(http:Caller caller, http:Request request) returns error? {
       check caller->respond("Hello World!");
   }
}

$ ballerina build hello.bal
Compiling source
    hello.bal

Generating executables
    hello.jar

Generating docker artifacts...
    @docker    	  - complete 2/2

    Run the following command to start a Docker container:
    docker run -d -p 8080:8080 hello:v1.0

Kubernetesはデプロイを自動化し、コンテナ化されたアプリケーションのスケーリングと管理を行うオープンソースのプラットフォームだ。Kubernetesでプログラムをデプロイして実行するため、開発者は一連のYAMLファイルを作成する必要がある。平均的な開発者にとって、これらYAMLファイルの作成は簡単ではない。Ballerinaコンパイラはソースコードをコンパイルしながら、これらYAMLファイルを作成することができる。


import ballerina/http;
import ballerina/kubernetes;

@kubernetes:Service {
   serviceType: "NodePort"
}
@kubernetes:Deployment {
   name: "hello"
}
service Hello on new http:Listener(8080) {
   resource function hi(http:Caller caller, http:Request request) returns error? {
       check caller->respond("Hello World!");
   }
}

Ballerinaサービスに@kubernetes:Deployment{}アノテーションを追加すると、Kubernetesにhelloアプリケーションをデプロイするのに必要なKubernetes Deployment YAMLが生成される。@kubernetes:Service{}アノテーションを追加すると、Kubernetes Service YAMLが生成される。ここではnodeIP:Port経由でhelloサービスにアクセスするため、serviceTypeを`NodePort`と設定している。

$ ballerina build hello.bal
Compiling source
    hello.bal

Generating executables
    hello.jar

Generating artifacts...

    @kubernetes:Service    	- complete 1/1
    @kubernetes:Deployment    	- complete 1/1
    @kubernetes:Docker    		- complete 2/2
    @kubernetes:Helm    		- complete 1/1

    Run the following command to deploy the Kubernetes artifacts:
    kubectl apply -f hello/kubernetes

    Run the following command to install the application using Helm:
    helm install --name helloworld hello/kubernetes/helloworld

同様に、開発者はBallerinaのビルトインアノテーションを使って、プログラムをOpenShiftIstioKnativeのようなプラットフォームや、AWS Lambda functionsとしてデプロイすることができる。

シーケンス図

シーケンス図は、サービスがどのように相互作用するかを視覚的に説明するのに最良の方法だ。並列性とネットワークに関するBallerinaの抽象化と構文は、シーケンス図と密接に対応するよう設計されている。Ballerinaでは、リモートメソッドは非リモートメソッドとは異なる構文 (->) を用いて呼び出される。シーケンス図では、ワーカーのライフラインからクライアントのオブジェクトライフラインへの横向きの矢印として描かれる。

一般的に、開発者が一度コードを書くと、そのコードは何度も読み返される。多くの場合、最初にコードを書いた開発者ではなく、別の開発者がそのコードを読むことになる。このような自動化されたビジュアル表現は、開発者がプログラムのやり取りを理解するのに役立つ。

Ballerina IDEプラグイン(例えば、VSCode plugin)はソースコードから動的にシーケンス図を生成することができる。

もっと詳しく

この記事では、Ballerinaのユニークな機能によって、アプリケーション開発者がどのようにクラウドネイティブなアプリケーションを書けるようになるのか見てきた。

言語設計の原則についてもっと広く知りたければ、Ballerina言語仕様を参照しよう。

JSON/XMLなどビルトインのデータ型やその他ネットワークベースの機能に関する使用例は、Ballerina by Exampleページで見ることができる。

著者について

Lakmal Warusawithana氏:WSO2のSenior Director/Developer Relations。オープンソース、クラウド、DevOps技術に長年の経験があり、Apache Stratos PaaSプロジェクトのVice Presidentを務めている。ネットワーク分散アプリケーションのためのオープンソースプログラミング言語であるBallerinaのコンテナ化とデプロイメントオーケストレーションのアーキテクトでもある。また、ApacheCon、CloudOpen、QCon、JaxLondon、Cloud Expo、Cloudstack Collaboration Conference、WSO2Con、KubeCon、ContainerCamp、DeveloperWeek、API Summit、その他多数のテック系ミートアップなど、多数のイベントで発表している。

 

この記事に星をつける

おすすめ度
スタイル

BT