Scala ile Tapir Kullanarak HTTP API'si Nasıl Oluşturulur ve Test Edilir

INEZA Felin-Michel

INEZA Felin-Michel

22 May 2026

Scala ile Tapir Kullanarak HTTP API'si Nasıl Oluşturulur ve Test Edilir

Kurumsal Apidog

Şirket İçi Dağıtım

SSO & RBAC

SOC 2 Uyumlu

Apidog Enterprise'ı Keşfet

Tapir, Typed API descRiptions'ın kısaltmasıdır. Bir HTTP uç noktasını düz bir değer olarak tanımlamanızı sağlayan bir Scala kütüphanesidir: yöntemi, yolu, girdileri, çıktıları ve hata durumları hepsi tip sisteminde yakalanır. Tapir, bu tek açıklamayla sunucu rotasını, bir istemciyi ve bir OpenAPI belgesini türetir. Sözleşmeyi bir kez yazarsınız ve gerisi kendiliğinden gelir.

Tapir'i bir web çatısında doğrudan rotalar yazmaktan ayıran şey budur. Bir rota işleyici, HTTP kablolamasını lojikle birleştirir. Bir Tapir uç noktası bunları ayırır: açıklama bir değerdir, iş mantığı ayrı bir fonksiyondur ve Tapir bunları birleştirir. Bu eğitim, küçük bir görev API'si oluşturacak, bunu bir sunucuya bağlayacak, dokümanlar üretecek ve gerçek Scala koduyla test edecektir.

Tapir size ne sunar

Tapir, üç avantajı olan basit bir fikir üzerine kurulmuştur.

Birincisi, tip güvenliğidir. Bir uç noktanın girdileri ve çıktıları tiplidir, bu nedenle derleyici işleyicinizin doğru şekli döndürdüğünü kontrol eder. Uç noktanın vaat ettikleri ile mantığın ürettikleri arasındaki bir uyumsuzluk, bir üretim hatası değil, bir derleme hatasıdır.

İkincisi, tek bir doğruluk kaynağıdır. Uç nokta bir değer olduğundan, OpenAPI belirtimini, sunucu yorumlayıcısını ve bir istemciyi aynı açıklamadan üretirsiniz. Yalnızca bir açıklama olduğu için birbirlerinden uzaklaşamazlar. Bu, API sözleşme testinin arkasındaki disiplinle aynıdır, derleyici tarafından uygulanır.

Üçüncüsü, çatı bağımsızlığıdır. Uç nokta açıklaması, Akka HTTP, Pekko, http4s veya Netty üzerinde çalışıp çalışmadığını bilmez. Sunucu yorumlayıcısını ayrı ayrı seçersiniz, böylece açıklama herhangi bir çatı seçiminden daha uzun süre yaşar.

Tapir'in ne olmadığı konusunda net olmakta fayda var. Bu bir web çatısı değildir; soketi, iş parçacığı havuzunu veya yönlendirme motorunu ellemez. Seçtiğiniz bir çatının üzerinde oturan bir açıklama katmanıdır. Ayrıca, olağan anlamda bir kod oluşturucu da değildir. Daha sonra düzenleyeceğiniz Scala dosyaları yazan bir araç çalıştırmazsınız. Uç nokta değerleri kaynaktır ve sunucu, istemci ve dokümanlar derleme zamanında onlardan hesaplanır. Bu, açıklama ile çalışan kodun senkronizasyon dışı kalmasını önler, bu da oluşturucu tabanlı iş akışlarının tekrarlayan bir arıza modudur, çünkü oluşturulan dosya elle düzenlenir ve kaynak farklılaşır.

Projeyi kurma

Tapir'i build.sbt dosyasına ekleyin. Çekirdek modüle, bir OpenAPI modülüne, bir sunucu yorumlayıcısına ve bir JSON kütüphanesine ihtiyacınız var. Bu örnek, JSON için circe ile birlikte Akka HTTP'nin aktif olarak sürdürülen halefi olan Pekko HTTP'yi kullanır.

val tapirVersion = "1.11.7"

libraryDependencies ++= Seq(
  "com.softwaremill.sttp.tapir" %% "tapir-core" % tapirVersion,
  "com.softwaremill.sttp.tapir" %% "tapir-pekko-http-server" % tapirVersion,
  "com.softwaremill.sttp.tapir" %% "tapir-json-circe" % tapirVersion,
  "com.softwaremill.sttp.tapir" %% "tapir-openapi-docs" % tapirVersion,
  "com.softwaremill.sttp.apispec" %% "openapi-circe-yaml" % "0.11.3"
)

Ekosistem hızlı hareket ettiğinden, tapir.softwaremill.com adresindeki mevcut Tapir belgeleri her modülü ve eşleşen sürümleri listeler.

Tipli bir uç nokta tanımlama

Alan adının tipleri ve bunların JSON kodlayıcılarıyla başlayın. circe, durum sınıflarından kodlayıcıları türetir.

import io.circe.generic.auto._
import sttp.tapir._
import sttp.tapir.json.circe._
import sttp.tapir.generic.auto._

case class Task(id: Int, title: String, done: Boolean)
case class NewTask(title: String)
case class ApiError(message: String)

Şimdi uç noktaları açıklayın. Her biri, endpoint tabanına birleştiricileri zincirleyerek oluşturulan bir değerdir. Zinciri bir cümle olarak okuyun: bu, bir tamsayı yol girişi alan ve 404 ile bir ApiError veya 200 ile bir Task döndüren /tasks/{id} adresine bir GET isteğidir.

val getTask: PublicEndpoint[Int, ApiError, Task, Any] =
  endpoint.get
    .in("tasks" / path[Int]("id"))
    .errorOut(statusCode(sttp.model.StatusCode.NotFound).and(jsonBody[ApiError]))
    .out(jsonBody[Task])

val listTasks: PublicEndpoint[Unit, Unit, List[Task], Any] =
  endpoint.get
    .in("tasks")
    .out(jsonBody[List[Task]])

val createTask: PublicEndpoint[NewTask, Unit, Task, Any] =
  endpoint.post
    .in("tasks")
    .in(jsonBody[NewTask])
    .out(statusCode(sttp.model.StatusCode.Created).and(jsonBody[Task]))

PublicEndpoint üzerindeki dört tip parametresi, girdi, hata çıktısı, başarı çıktısı ve gerekli yeteneklerdir. Henüz hiçbir şey çalışmıyor. Bunlar saf açıklamalardır, bu yüzden aynı değerler bir sunucu, bir istemci ve belgeler üretebilir.

Birkaç birleştirici ağırlığın çoğunu taşır. in, bir yol segmenti, bir sorgu parametresi, bir başlık veya bir gövde olsun, bir girdi ekler. out, başarı yanıtını tanımlar. errorOut, hata yanıtını tanımlar ve tek bir uç nokta, varyantları oneOf ile birleştirerek birkaç hata durumunu modelleyebilir. Her birleştirici, uç noktanın tipini daraltır, böylece açıklama tamamlandığında, tip imzası sözleşmenin kesin bir ifadesidir. getTask'ın imzasını okuyabilir ve bir Int aldığını, bir ApiError ile başarısız olabileceğini ve aksi takdirde bir Task döndürdüğünü, tek bir uygulama satırı okumadan bile bilirsiniz.

Bu, Tapir'in pratik kalbidir. Uç nokta açıklaması, derleyicinin uyguladığı bir belgedir. Bir ekip arkadaşı sözleşmeyi yanlış okuyamaz, çünkü sözleşme bir tiptir ve onu ihlal eden kod derlenmez.

Sunucu mantığı ekleme

Bir açıklama, serverLogic ile mantık eklediğinizde çalışan bir rotaya dönüşür. Mantık, Future gibi bir etki tipine sarılmış, Either[ErrorType, SuccessType] döndüren bir fonksiyondur. Basit bir bellek içi depolama, örneği kendi kendine yeterli tutar.

import scala.concurrent.Future
import scala.collection.concurrent.TrieMap
import sttp.tapir.server.ServerEndpoint

val store = TrieMap[Int, Task](
  1 -> Task(1, "Write the Tapir tutorial", done = false)
)

val getTaskServer: ServerEndpoint[Any, Future] =
  getTask.serverLogic { id =>
    Future.successful(
      store.get(id).toRight(ApiError(s"No task with id $id"))
    )
  }

val listTasksServer: ServerEndpoint[Any, Future] =
  listTasks.serverLogic(_ => Future.successful(Right(store.values.toList)))

val createTaskServer: ServerEndpoint[Any, Future] =
  createTask.serverLogic { newTask =>
    val id = store.size + 1
    val task = Task(id, newTask.title, done = false)
    store.put(id, task)
    Future.successful(Right(task))
  }

Derleyici burada sözleşmeyi uygular. getTask, ApiError tipinde bir hata vaat eder, bu nedenle toRight bir ApiError sağlamalıdır. Yanlış tip döndürürseniz kod derlenmez. HTTP detayları, 404, JSON kodlaması zaten açıklama tarafından ele alınmıştır.

Sunucu uç noktalarını Pekko yorumlayıcısı ile gerçek rotalara dönüştürün ve sunucuyu başlatın.

import sttp.tapir.server.pekkohttp.PekkoHttpServerInterpreter
import org.apache.pekko.http.scaladsl.Http
import org.apache.pekko.actor.typed.ActorSystem
import org.apache.pekko.actor.typed.scaladsl.Behaviors

implicit val system: ActorSystem[Any] = ActorSystem(Behaviors.empty, "task-api")
import system.executionContext

val routes = PekkoHttpServerInterpreter().toRoute(
  List(getTaskServer, listTasksServer, createTaskServer)
)

Http().newServerAt("localhost", 8080).bind(routes)

API artık 8080 portunda cevap veriyor. Çerçeve endişesini tek bir yerde, yorumlayıcıda değiştirdiniz, tek bir uç nokta açıklamasına dokunmadan.

OpenAPI belgeleri oluşturma

Uç noktalar değerler olduğu için, OpenAPI belgesi bunların bir fonksiyonudur. Açıklama yok, ayrı bir belirtim dosyası yok.

import sttp.tapir.docs.openapi.OpenAPIDocsInterpreter
import sttp.apispec.openapi.circe.yaml._

val docs = OpenAPIDocsInterpreter().toOpenAPI(
  List(getTask, listTasks, createTask),
  "Task API",
  "1.0"
)

println(docs.toYaml)

docs.toYaml, eksiksiz bir OpenAPI belgesi üretir. tapir-swagger-ui-bundle modülünü ekleyin ve Tapir, doğrudan sunucunuzdan etkileşimli bir Swagger UI sunar. Belirtim API hakkında yalan söyleyemez, çünkü her ikisi de aynı uç nokta değerlerinden gelir.

Bu OpenAPI dosyası, diğer araçlarla köprünüzdür. Apidog'a aktarın ve gezinilebilir bir API referansı, manuel kontroller için bir istek konsolu ve oluşturulmuş bir mock sunucusu elde edersiniz, hepsi Scala kodunuzun zaten ürettiği belirtimden ayrılmadan. Bu, ön uç ekip arkadaşlarına Scala hizmeti gelişirken üzerinde çalışacakları bir şey vermek için pratik bir yoldur.

Burada bahsedilmesi gereken bir iş akışı var. Tapir belirtimi Scala kaynak kodunuzdan üretir, bu nedenle belirtim her zaman günceldir. Bu belirtimi tüketen bir mocking aracı, gerçek sunucu herhangi bir yere dağıtılmadan önce ön ucunuza çalışan bir uç nokta sağlar. Sözleşme tek yönde akar: Scala tipleri onu tanımlar, OpenAPI onu taşır ve mock ile ön uç onu tüketir. Kimse sözleşmeyi iki kez elle yazmaz, bu nedenle kimse iki kopyayı senkronizasyon dışı bırakamaz. API'leri diğer ekiplerin bağımlı olduğu Scala ekipleri için, OpenAPI dışa aktarımını derlemeye bağlı tutmanın ana nedeni budur.

API'yi test etme

Tapir uç noktaları iki seviyede test edilebilir.

İlk seviye, stub yorumlayıcıyı kullanarak hiçbir HTTP olmadan uç nokta mantığını kontrol eder. Sunucu uç noktalarından bir test arka ucu oluşturur ve bir sttp istemcisi aracılığıyla istekler gönderirsiniz. Bu hızlıdır çünkü hiçbir şey bir port bağlamaz.

import org.scalatest.flatspec.AsyncFlatSpec
import org.scalatest.matchers.should.Matchers
import sttp.client3._
import sttp.client3.testing.SttpBackendStub
import sttp.tapir.server.stub.TapirStubInterpreter
import sttp.client3.circe._
import io.circe.generic.auto._

class TaskApiSpec extends AsyncFlatSpec with Matchers {

  val backend = TapirStubInterpreter(SttpBackendStub.asynchronousFuture)
    .whenServerEndpointRunLogic(getTaskServer)
    .whenServerEndpointRunLogic(createTaskServer)
    .backend()

  "GET /tasks/1" should "return the seeded task" in {
    basicRequest
      .get(uri"http://test.com/tasks/1")
      .response(asJson[Task])
      .send(backend)
      .map { resp =>
        resp.code.code shouldBe 200
        resp.body.map(_.title) shouldBe Right("Write the Tapir tutorial")
      }
  }

  "GET /tasks/999" should "return a 404 with an error body" in {
    basicRequest
      .get(uri"http://test.com/tasks/999")
      .response(asJson[ApiError])
      .send(backend)
      .map { resp => resp.code.code shouldBe 404 }
  }
}

İkinci seviye tam bir entegrasyon testidir: gerçek sunucuyu bir porta bağlayın ve bir HTTP istemcisiyle çağırın. Bunu, kablolamayı doğrulamak için seyrek kullanın ve hızları nedeniyle kapsamanızın büyük bir kısmı için stub tabanlı testlere güvenin. Hızlı izole kontroller ile ince bir gerçek kontroller katmanı arasındaki ayrım, genel olarak otomatik testteki iyi uygulamaları yansıtır.

Tapir'in tasarımından faydalı bir özellik ortaya çıkıyor: uç nokta açıklaması paylaşıldığı için, getTask'dan istekler oluşturan bir test, üretim sunucusunun reddedeceği bir istek gönderemez. Sözleşme, her iki tarafta da aynı değer tarafından uygulanır. Bu sözleşmeyi manuel keşif ve bir mock sunucusu aracılığıyla geri döndürmek için, Apidog'u indirin ve Tapir'in oluşturduğu OpenAPI dosyasını içe aktarın.

Sıkça sorulan sorular

Scala'da Tapir nedir?

Typed API descRiptions'ın kısaltması olan Tapir, HTTP uç noktalarını tipli değerler olarak tanımlamak için bir Scala kütüphanesidir. Tek bir açıklamadan sunucu rotasını, bir istemciyi ve bir OpenAPI belgesini türetir, böylece sözleşme bir kez tanımlanır ve her üçünde de tutarlı kalır.

Tapir ile Akka HTTP kullanmak zorunda mıyım?

Hayır. Tapir, uç nokta açıklamasını sunucu yorumlayıcısından ayırır. Farklı bir yorumlayıcı modülü seçerek aynı uç noktaları Pekko HTTP, http4s, Netty, Vert.x veya diğerlerinde çalıştırabilirsiniz. Açıklamalar değişmez.

Tapir OpenAPI belgelerini nasıl oluşturur?

Tapir'in OpenAPI modülü, uç nokta değerlerinizi okur ve bunlardan eksiksiz bir OpenAPI belgesi üretir. Belgeler ve sunucu aynı açıklamalardan türetildiği için, belgeler gerçek API ile senkronizasyon dışı kalamaz.

Bir sunucu başlatmadan bir Tapir API'yi nasıl test ederim?

Tapir stub yorumlayıcısını kullanın. Sunucu uç noktalarınızdan bir sttp test arka ucu oluşturur, böylece bir port bağlamadan istekler gönderebilir ve yanıtlara göre iddialarda bulunabilirsiniz. Bu testler hızlıdır ve gerçek kablolamayı doğrulamak için az sayıda tam entegrasyon testi tutarsınız.

Tapir'in çıktısını diğer API araçlarıyla kullanabilir miyim?

Evet. Tapir'in oluşturduğu OpenAPI belgesi standart bir belirtimdir. Etkileşimli bir API referansı, bir istek konsolu ve oluşturulmuş bir mock sunucusu elde etmek için Apidog gibi araçlara aktarabilirsiniz, bu da API'yi ön uç geliştiricilerle paylaşmak için faydalıdır.

API Tasarım-Öncelikli Yaklaşımı Apidog'da Uygulayın

API'leri oluşturmanın ve kullanmanın daha kolay yolunu keşfedin