Dapr: Service Invocation

Mikroservis tabanlı uygulamalarda, birden çok servisin birbiri ile haberleşmesine ihtiyaç vardır. Bu iletişim gereksinimi de beraberinde aşağıdaki sorunları getirmektedir.
- Servis Keşfi
- Servisler arası API ortaklaştırılması
- Servisler arası güvenli iletişim
- Servisler arası iletişimde zaman aşımı veya hatalar
- Servisler arası iletişimlerin gözlemlenebilirliği
Service Invocation API
Dapr, yukarıda bahsedilmiş olan sorunların çözümü için servislerin çağrılması ve iletişimini sağlayan bir API sunmaktadır.
API, Dapr instance’ında kullanılır. Her uygulama kendi Dapr instance’ı ile, her Dapr instance’ı da kendi içerisinde biribiri ile iletişim kurmaktadır. Aşağıdaki diyagramda iletişim adımları gösterilmiştir.
Adım | Açıklama |
---|---|
1 | Servis A, Servis B ye gönderilmek üzere HTTP veya gRPC çağrımı (call) yapar. Bu çağrım, öncelikli olarak localde bulunan Dapr sidecar’lara iletilir. |
2 | Name resolution component kullanılarak, Service B’nin adresi Dapr ile çözümlenir. |
3 | Localdeki Servis A ya ait Dapr sidecar’ı, almış olduğu mesajı Service B’ye ait sidecar’a gönderir. (Sidecar’lar arasında gRPC kullanılır). |
4 | Service B’ye ait sidecar, almış olduğu mesajı Servis B’ye iletir. Mesaj servis içerisinde işlenir. |
5 | Service B, gelen mesaja ait cevabı Service A’ya iletilmek üzere kendi Dapr sidecar’ına iletir. |
6 | Service B’ye air sidecar, mesajı Service A’nın sidecar’ına gönderir. |
7 | Servis A cevabı alır. |
HTTP and gRPC Service Invocation : HTTP ve gRPC protokolleri kullanılmaktadır.
Service-to-service Security : mTLS kullanılmaktadır.
- Servis çağrı hataları vb. durumda Dapr, belirli aralıklarda yeniden denemenin sağlanacağı bir özellik sunar.
- Dapr’ın varsayılan özelliği olarak servisler arası çağrılar gözlemlenebilmektedir. Grafik vb. sunmaktadır.
- Erişim kontrollerini sağlar. Uygulamaların birbirine erişiminin kısıtlanacağı veya izin verileceği durumlar tanımlanabilmektedir.
mDNS ile load balancing sağlar. Aşağıdaki grafikte nasıl sağlandığı gösterilmiştir. Örneğin 3 instance olan uygulamada Service A’dan gelen istek, diğer 3 servisten herhangi birine load balancing uygulanarak iletilir.
Uygulama Id leri, kaç instance olduğuna bakılmaksızın benzersizdir. Uygulamaya ait farklı instance’lar aynı Id’yi paylaşmaktadır.
Dapr’ın sağlamış olduğu hizmetlerden biri olan Service Invocation’ı gerçekleştirebilmek için iki farklı yöntem (HTTP ve gRPC) mevcuttur. Bunlardan biri HTTP kullanarak servis çağrısı yapmaktır.
Örnek Uygulama Mimarisi
Farklı Alan Adları Arasında Servis Çağrısı Yapmak
Aynı alan adı içerisinde bulunan servisler arasında servis çağrısı yapmak için uygulama adı ile referans vermek varsayılan olarak yeterlidir.
1
localhost:3500/v1.0/invoke/nodeapp/method/neworder
Servis çağrıları farklı alan adları arasında da yapılabilir. Desteklenen tüm platformlarda Dapr uygulama adları ile hedef alan adını içeren geçerli bir FQDN formatı kullanılarak servis çağrısı yapılabilir. FQDN formatı şu şekildedir:{Uygulama Adı}.{Uygulamanın Bulunduğu Alan Adı}
1
localhost:3500/v1.0/invoke/nodeapp.production/method/neworder
HTTP Kullanarak Servis Çağrısı Yapmak
Service invocation’ı gerçekleştirebilmek için kullanılacak iki servisin de dapr sidecar’ına sahip olması gerekmektedir. Kullanımı şu şekilde olur;
1
2
3
dapr run --app-id checkout --app-port 6002 --dapr-http-port 3602 --dapr-grpc-port 60002 --app-ssl mvn spring-boot:run
dapr run --app-id orderprocessing --app-port 6001 --dapr-http-port 3601 --dapr-grpc-port 60001 --app-ssl mvn spring-boot:run
Görselde görülen iki farklı service’i dapr ile beraber kaldırılması gerekmektedir. Bunu yukarda tanımladığımız scriptler sayesinde gerçekleştiririz. Scriptleri detaylı inceleyecek olursak, ilk önce dapr applicationlara global unique bir id belirlememiz gerekmektedir. Bu service idleri dapr için önemlidir çünkü bir servisin hangi servisi tetikleyeceğini bu idleri kullanarak bilir.
- app-port içerde yazdığımız uygulamanın ayakta olduğu portu tanımlamak için kullanılır.
- dapr-http-port uygulamamız kendi sidecar’ına erişirken kullanacağı portu, sidecar’ın http için dinleyeceği portu tanımlar. Servisleri dışardan tetiklemek istediğimizde bu portu kullanırız.
- dapr-grpc-port uygulamamızın kendi sidecar’ına erişirken kullanacağı portu, sidecar’ın grpc için dinleyeceği portu tanımlar. Servisleri dışardan tetiklemek istediğimizde bu portu kullanırız.
- app-ssl eğer erişimi ssl ile sağlamak istiyorsak bu şekilde app-ssl komutu ekleyerek ssl ile beraber gerçekleştirilebilmesini sağlayabiliriz.
Uygulamamız içinden bu yöntemi kullanmak istediğimdeki örnek java kodu şu şekilde olmaktadır;
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
//dependencies
import io.dapr.client.DaprClient;
import io.dapr.client.DaprClientBuilder;
import io.dapr.client.domain.HttpExtension;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Random;
import java.util.concurrent.TimeUnit;
//code
@SpringBootApplication
public class OrderProcessingServiceApplication {
private static final Logger log = LoggerFactory.getLogger(OrderProcessingServiceApplication.class);
public static void main(String[] args) throws InterruptedException{
while(true) {
TimeUnit.MILLISECONDS.sleep(5000);
Random random = new Random();
int orderId = random.nextInt(1000-1) + 1;
DaprClient daprClient = new DaprClientBuilder().build();
//Using Dapr SDK to invoke a method
var result = daprClient.invokeMethod(
"checkout",
"checkout/" + orderId,
null,
HttpExtension.GET,
String.class
);
log.info("Order requested: " + orderId);
log.info("Result: " + result);
}
}
}
invokeMethod fonksiyonunun ilk parametresi çağırmak istediğimiz servisi, ikinci parametresi ise o serviste tetikleyeceğimiz URL’i tanımlamaktadır. Diğer parametreler ise atılacak isteğin tipi ve dönüş değeri ile ilgilidir. İsteği alan taraf için ekstra bir tanımlama yapmamıza bir gerek yoktur, orada süreç aynı sıradan bir controller gibi işlemeye devam edecektir.
Burda yaptığımız isteği CLI’dan da tetiklemek istediğimizde aşağıdaki şekillerde curl istekleri işimizi görecektir;
1
2
3
curl 'http://dapr-app-id:checkout@localhost:3602/checkout/100' -X POST
curl -H 'dapr-app-id: checkout' 'http://localhost:3602/checkout/100' -X POST
curl http://localhost:3602/v1.0/invoke/checkout/method/checkout/100
Bu isteklerin tamamı aynı anlama gelmektedir çağıracağımız servisin adresini çeşitli yollarla curl isteğinde belirtebiliriz.
Bunların yanında dapr’ın built-in invoke metodunu kullanarak da bu işlemi gerçekleştirebiliriz.
1
dapr invoke --app-id checkout --method checkout/100
gRPC ile Servis Çağrısı Yapmak
Dapr service çağrısı faydaları aşağıdaki gibidir:
- Karşılıklı kimlik doğrulama
- İzleme
- Metrikler
- Erişim listeleri
- Ağ düzeyinde esneklik
- API token tabanlı kimlik doğrulama
Adım 1: gRPC Server Çalıştır
Bu Go uygulaması, Greeter proto servisini implement eder.
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
package main
import (
"context"
"log"
"net"
"google.golang.org/grpc"
pb "google.golang.org/grpc/examples/helloworld/helloworld"
)
const (
port = ":50051"
)
// server is used to implement helloworld.GreeterServer.
type server struct {
pb.UnimplementedGreeterServer
}
// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
log.Printf("Received: %v", in.GetName())
return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}
func main() {
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterGreeterServer(s, &server{})
log.Printf("server listening at %v", lis.Addr())
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
Dapr CLI kullanarak, uygulamaya –app-id flagini kullanarak, sunucuya unique id atadık.
1
dapr run --app-id server --app-port 50051 -- go run main.go
Adım 2: Servis Çağrısı
Aşağıdaki örnek, bir gRPC istemcisinden Dapr kullanarak Greeter hizmetini nasıl keşfedeceğinizi gösterir. Hedef hizmeti doğrudan 50051 portunu çağırmak yerine, istemcinin 50007 portu üzerinden yerel Dapr sidecar çağırdığını gözlemliyoruz.
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
package main
import (
"context"
"log"
"time"
"google.golang.org/grpc"
pb "google.golang.org/grpc/examples/helloworld/helloworld"
"google.golang.org/grpc/metadata"
)
const (
address = "localhost:50007"
)
func main() {
// Set up a connection to the server.
conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewGreeterClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
defer cancel()
ctx = metadata.AppendToOutgoingContext(ctx, "dapr-app-id", "server")
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: "Darth Tyrannus"})
if err != nil {
log.Fatalf("could not greet: %v", err)
}
log.Printf("Greeting: %s", r.GetMessage())
}
Aşağıdaki satır, Dapr’a server adlı bir uygulamayı keşfetmesini ve çağırmasını söyler:
1
ctx = metadata.AppendToOutgoingContext(ctx, "dapr-app-id", "server")
Dapr CLI kullanarak istemciyi çalıştırmak için aşağıdaki komut yazılır:
1
dapr run --app-id client --dapr-grpc-port 50007 -- go run main.go