Rapidrows: Postgres için Kolay API Server

RapidRows, PostgreSQL üzerinde sorgular çalıştırmak, scheduled job’lar gerçekleştirmek ve PostgreSQL bildirimlerini websocket’lara iletmek gibi kullanım durumlarına göre yapılandırılabilen açık kaynak kodlu C ve Go dillerinde yazılmış, bağımlılığı olmayan, düşük gecikmeli ve tek dosya olarak çalışan bir API sunucusudur.
Genel Bakış
RapidRows, low-code yapılandırılabilen bir API sunucusudur. PostgreSQL üzerindeki verileri okumak / işlemek amacıyla bir API sunucusu oluşturmanın en kolay yolu olmayı hedefliyor.
- PostgreSQL için Tasarlandı: RapidRows, PostgreSQL ve sayısı giderek artan PostgreSQL uyumlu uygulamalar için tasarlanmıştır. Dahili connection pooler yapısına, PostgreSQL’ten gelen bildirimleri WebSockets ve Server Sent Events kanallarına iletme gibi özelliklere sahiptir.
- Veritabanı Sorgulama Esnekliği Sağlar: Veritabanı şemasını inceleyerek otomatik bir REST veya GraphQL API sağlayan araçların aksine, RapidRows, SQL sorgularını kendi üzerinde çalıştırır. Bu, kullanıcıların özellikle OLAP kullanım durumları için karmaşık SQL sorguları yazmasını, test etmesini ve çalıştırmasını sağlar. Çok daha basit oluşturulan REST API’lar ile çalışılmasına olanak tanır.
- Scheduled jobs: PostgreSQL veritabanı yönetiminde periyodik işler yürütmek çok kolaylaştırılmıştır. RapidRows, SQL sorguları ve JavaScript kodu çalıştırabilen CRON benzeri bir scheduler’a sahiptir. Partition’lar oluşturma, materialized view’ları yenileme vb. görevler planlanabilir.
- Kabiliyetleri: Yapılandırılabilir CORS, yanıt sıkıştırma, parametre doğrulama, sorgu sonuçlarının sunucu tarafında önbelleğe alınması, sorgu zaman aşımı ayarlanabilmesi, transaction seçenekleri, connection pooling ve her bir endpoint için ayrı hata ayıklama günlüğü oluşturma. Ek doğrulamalar ve sorgu sonuçlarını değiştirebilmeniz için QuickJS JavaScript engine kullanımını sunar.
- Öğrenimi Kolaydır: RapidRows, kolayca deploy edilebilir bir CLI aracıdır. Açık kaynaklı ve Apache 2.0 altında lisanslanmıştır. Herhangi bir PostgreSQL eklenti kurulumu gerektirmez.
- Kullanımı Kolaydır: Bir JSON veya YAML dosyası ile yapılandırılır ve komut satırında yalnızca rapidrows
komutu ile deploy edilir.
Mimari
Nasıl Çalışır?
RapidRows’un en büyük parçası yapılandırılabilir HTTP sunucusudur. HTTP hizmetinde kullanılan yollar (paths) Swagger‘a çok benzer şekilde olmakla birlikte RapidRows yapılandırması bu yola nasıl yanıt verileceğini de belirtir. RapidRows terminolojisinde, yol bir endpoint’dir. Bir endpoint’teki yanıt şu şekillerde olabilir:
1- PostgreSQL sunucusunda bir sorgu çalıştırma ve sonucu JSON biçiminde döndürme,
2- Sorguyu çalıştırıp sonucu CSV biçiminde döndürme,
3- Sorguyu çalıştırıp yalnızca etkilenen satır sayısını dönderme (UPDATE gibi herhangi bir veri döndürmeyen sorgular için)
4- JavaScript kod yardımıyla gelen istekler kontrol edebilir, hangi sorguların çalıştırılacağına, hangi çıktının döndürüleceğine karar verebilir.
5- Text veya JSON statik veri döndürülebilir.
Parameters
Endpoint’ler parametrelere sahip olabilir. RapidRows parametreleri URI yolunun bir parçası, sorgu parametresi veya JSON ya da POST form body olarak verilebilir. Parametreler sayı (integer veya float), string, boolean, veya array olabilir. Array’ler yalnızca sayı, tamsayı, boolean içerebilir. Nested arrays ve nesneler desteklenmez.
Min/max değer, uzunluk ve array boyutu, regex ve enumerated liste gibi validation kuralları parametre özelinde belirtilebilir.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
version: '1'
endpoints:
- uri: /movies/by-genre-and-year
implType: query-json
script: |
select F.title, C.name, F.release_year
from film F
join film_category FC on F.film_id = FC.film_id
join category C on FC.category_id = C.category_id
where C.name = any($1::text[])
and F.release_year = $2
datasource: pagila
params:
- name: genres
in: query
type: array
elemType: string
minItems: 1
required: true
- name: year
in: query
type: integer
minimum: 1952
maximum: 2022
datasources:
- name: 'pagila'
dbname: 'pagila'
Dataset: Pagila.
Parametre olarak verilen yıl ve türde vizyona giren filmler:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
$ curl -i 'http://localhost:8080/movies/by-genre-and-year?genres=Sci-Fi&genres=Comedy&year=2006'
HTTP/1.1 200 OK
Content-Type: application/json
Date: Tue, 28 Feb 2023 06:29:57 GMT
Transfer-Encoding: chunked
{
"rows": [
[
"AIRPLANE SIERRA",
"Comedy",
2006
],
[
"ANNIE IDENTITY",
"Sci-Fi",
2006
],
[
"ANTHEM LUKE",
"Comedy",
2006
],
[
"ATTACKS HATE",
"Sci-Fi",
2006
],
.
.
.
JavaScript API
JavaScript ortamı, ES2020 tarafından belirtilen tüm global nesnelerin yanı sıra RapidRows tarafından eklenen $sys adlı bir nesneye sahiptir. $sys nesnesi, bir endpoint veya scheduled job script olarak yürütülen JavaScript kodu tarafından kullanılabilir. Kullanım:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
version: '1'
endpoints:
- uri: /exec-js
implType: javascript
methods:
- POST
datasource: pagila
script: |
// get a connection to a datasource
let conn = $sys.acquire("pagila");
// perform a query
let genreResult = conn.query(`
select C.name
from rental R
join inventory I on R.inventory_id = I.inventory_id
join film_category FC on I.film_id = FC.film_id
join category C on C.category_id = FC.category_id
where R.rental_id = $1
`, $sys.params.rental_id);
// check the result
if (genreResult.rows.length != 1)
throw "Rental not found";
const genre = genreResult.rows[0][0];
// further checks
let today = (new Date()).getDay();
if (genre == "Horror" && today == 3)
throw "Cannot return Horror DVDs on Wednesdays!"
// exec a SQL without a resultset
conn.exec("UPDATE rental SET return_date = now() WHERE rental_id = $1",
$sys.params.rental_id)
params:
- name: rental_id
in: body
type: integer
minimum: 1
required: true
datasources:
- name: pagila
dbname: pagila
Kullanımı:
1
2
3
curl -i -X POST http://localhost:8080/exec-js -H 'Content-Type: application/json' -d '{ "rental_id": 3 }'
HTTP/1.1 204 No Content
Date: Wed, 21 Sep 2022 06:07:17 GMT
Eğer olmayan bir rental_id gönderirsek:
1
2
3
4
5
6
7
8
curl -i -X POST http://localhost:8080/exec-js -H 'Content-Type: application/json' -d '{ "rental_id": 17000 }'
HTTP/1.1 500 Internal Server Error
Content-Type: text/plain; charset=utf8
Date: Wed, 21 Sep 2022 06:07:24 GMT
Content-Length: 16
Rental not found
Yukarıdaki kodta belirli bir içerik türüne göre farklı hata üretmeyi denemek istersek
1
2
3
4
5
6
7
$ curl -i -X POST http://localhost:8080/exec-js -H 'Content-Type: application/json' -d '{ "rental_id": 4 }'
HTTP/1.1 500 Internal Server Error
Content-Type: text/plain; charset=utf8
Date: Wed, 21 Sep 2022 06:07:14 GMT
Content-Length: 40
Cannot return Horror DVDs on Wednesdays!
Caching
Her endpoint, sonuçları sunucu tarafında belirli bir saniye boyunca önbelleğe alacak şekilde yapılandırılabilir. Bu kullanımda RapidRows, endpoint’in ilk çağrılmasında üretilen son yanıtı bellekte önbelleğe alır. Sonraki çağrı için, önbellek zaman aşımı süresi dolmamışsa aynı yanıtı yeniden kullanır.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
version: '1'
endpoints:
- uri: /param-in-body
implType: query-json
datasource: pagila
script: |
SELECT title, description FROM film WHERE fulltext @@ to_tsquery($1) ORDER BY title ASC
params:
- name: descfts
in: body
type: string
required: true
cache: 3600
datasources:
- name: pagila
dbname: pagila
Transactions
Sorgular, endpoint yapılandırmasında transaction parametreleri belirtilerek bir transaction bağlamında çalıştırılabilir. PostgreSQL’de bir transaction read-only veya read-write olarak farklı ISO seviyelerinde (read-committed, repeatable-read or serializable) olabilir. Bir endpoint için transaction türü ayarlanarak SQL sorgusunun bu transaction içinde çalışması sağlanabilir.
Timeout
Endpoint çağrılarındaki sorgulamar için bir zaman aşımı belirtilebilir.
1
2
3
4
5
6
7
8
9
10
version: '1'
endpoints:
- uri: /query-timeout
implType: query-json
datasource: pagila
script: SELECT pg_sleep(60)
timeout: 5
datasources:
- name: pagila
dbname: pagila
Datasources ve Connection Pooling
RapidRows’ta datasource bir PostgreSQL veritabanı bağlantısına karşılık gelir. Veri kaynağı isteğe bağlı olarak connection pooller ile yapılandırılabilir. Sorgular datasource ismiyle verilen veri kaynağında çalıştırılır. Bir datasource tanımlamasında tüm libpq connection parametreleri tanımlanabilir. Connection pooller, RapidRows başlatıldığında veritabanına minimum sayıda bağlantı kuracak şekilde yapılandırılabilir. Opsiyonel olarak maksimum bir limiti aşmayacak şekilde, kullanılmayan veya idle durumdaki bağlantıları kapatacak şekilde yapılandırılabilir.
1
2
3
4
5
6
7
8
9
10
datasources:
- name: pagila-dev
dbname: pagila
host: dev.proj.example.com
- name: pagila-prod
dbname: pagila
host: pgbouncer.prod.example.com
params:
application_name: rapidrows
statement_timeout: 60
Notifications over WebSocket ve SSE
RapidRows, PostgreSQL channel’larına gönderilen bildirimleri WebSockets ve Server Sent Events‘e iletebilir. RapidRows terminolojisinde, bu tip Websocket ve SSE endpoint’ler streams olarak adlandırılır. Herhangi bir sayıda istemci, tek bir websocket/sse akışına bağlanabilir. NOTIFY veya pg_notify() kullanılarak gönderilen her bildirim stream’e bağlı istemcilere gönderilecektir.
1
2
3
4
5
6
7
8
9
version: '1'
streams:
- uri: '/new_payments'
type: 'websocket'
datasource: 'pagila'
channel: 'payment_received'
datasources:
- name: 'pagila'
dbname: 'pagila'
Scheduled jobs
RapidRows, scheduled job’ları çalıştırmak için CRON benzeri bir daemon sağlar . Benzer çözümlerin aksine veritabanı eklenti kurulumu gerektirmez ve veritabanında veri depolamaz. Job; görevin kendisi, SQL ifadeleri ya da JavaScript kodu olabilir. Schedule , standart CRON sözdizimi kullanılarak veya tekrarlama aralığı @every X şeklinde tanımlanabilir. X, saat, dakika ve saniye veya 10h3m5s gibi hepsi bir arada kullanılabilir. Endpoint’lere benzer şekilde, scheduled job’da kullanılan SQL deyimleri transaction seçeneklerine ve bir zaman aşımına sahip olabilir. JavaScript’de kullanarak gelecek ay için partition oluşturan bir job örneği:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
version: '1'
jobs:
- name: create-monthly-partition
type: javascript
schedule: '0 10 28 * *'
datasource: pagila
script: |
// Sonraki 2 ay için YYYY ve MM bul
const now = new Date();
const nextMonth = new Date(now.getFullYear(), now.getMonth()+1, 1);
const next2Month = new Date(nextMonth.getFullYear(), nextMonth.getMonth()+1, 1);
const y1 = nextMonth.getFullYear(), m1 = nextMonth.getMonth() + 1;
const y2 = next2Month.getFullYear(), m2 = next2Month.getMonth() + 1;
const m1s = (m1 < 10 ? '0' : '') + m1, m2s = (m2 < 10 ? '0' : '') + m2;
// Bir sonraki ay için partition oluşturan sql hazırla
const sql = `
CREATE TABLE public.payment_p${y1}_${m1s} (
payment_id integer DEFAULT nextval('public.payment_payment_id_seq'::regclass) NOT NULL,
customer_id integer NOT NULL,
staff_id integer NOT NULL,
rental_id integer NOT NULL,
amount numeric(5,2) NOT NULL,
payment_date timestamp with time zone NOT NULL
);
ALTER TABLE ONLY public.payment
ATTACH PARTITION public.payment_p${y1}_${m1s}
FOR VALUES FROM ('${y1}-${m1s}-01 00:00:00+00') TO ('${y2}-${m2s}-01 00:00:00+00');`
// sql'i çalıştır
$sys.acquire('pagila').exec(sql)
datasources:
- name: 'pagila'
dbname: 'pagila'
CORS
Cross Origin Resource Sharing, root seviyede yapılandırılarak tüm stream ve endpoint’ler için uygulanabilir. Herhangi bir URI’da erişilen origin, method ve header’lar ayarlanabilir. Response header olarak dönülen değer de yapılandırılabilir:
-
Access-Control-Expose-Headers
-
Access-Control-Max-Age
-
Access-Control-Allow-Credentials
CORS ayarı belirtilmediğinde yanıtlarda CORS ile ilgili header’lar bulunmaz. CORS ayarı boş bir nesne olarak ayarlanırsa CORS, URI’lar için tüm erişimlere izin verecek şekilde ayarlanacaktır. Bu durum güvenlik problemlerine sebep olabilir.
Build and Install
RapidRows pre-built binary sürümleri GitHub‘da bulunabilir. Kurulum için Go compiler v1.18 veya üstü ve gcc/clang gerekir.
1
go install github.com/rapidloop/rapidrows/cmd/rapidrows@latest
1
2
$ rapidrows --yaml hello.yaml
2022-09-26 07:49:44.887 INF API server started successfully listen=127.0.0.1:8080
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[Unit]
Description=RapidRows service 1
After=network.target
[Service]
ExecStart=/usr/local/sbin/rapidrows path/to/config.json
WorkingDirectory=/
StandardOutput=append:/path/to/log/file
Restart=on-failure
RestartSec=5s
User=www-data
[Install]
WantedBy=multi-user.target
Kaynak
https://rapidrows.io/