Polyglot microservices rendszerekben az interfészdefiníciókkal van a legtöbb baj.
C#-ban megírod az IOrderService-t, aztán átírod Java-ban, aztán Python-ban.
Három fájlt tartasz karban három helyen, és előbb-utóbb az egyik eltér a többitől.
A hiba a rendszerhatáron jön elő, éles környezetben, amikor a két oldal az interfész
két különböző verzióját használja.
A forrás egyetlen legyen
A UniContract ezt oldja meg. Az interfészt egyszer írod le egy .contract fájlban,
és a tool kigenerálja minden célplatformra. Kézzel nem kell szinkronizálni, ezt a generátor végzi,
ezért a platformok változatai nem térhetnek el egymástól.
Így néz ki egy contract definíció:
namespace DataStoring {
interface IDataStore {
readonly property Connection: IConnection
async method IsCollectionExists(name: string) => boolean
async method GetCollectionByName<T constraint IEntity>(name: string) => ICollection<T>
}
}
Ebből a definícióból C# interfész, Java interface és Python abstract class generálható. Mindhárom ugyanazt a contractot valósítja meg, és ha a contract megváltozik, az újragenerálás után mindhárom vele változik.
A fordítóhoz hasonló pipeline
Az architektúra egy fordítóét követi. ANTLR 4 alapú lexer és parser dolgozza fel
a .contract fájlokat, a syntax tree-ből element builder épít szemantikus modellt,
a semantic checker ellenőrzi az összefüggéseket, végül a cserélhető (pluggable) emitterek
generálják a célnyelvi kódot.
Az emitterek Python modulok, így bárki írhat sajátot anélkül, hogy a core engine-hez nyúlna. Ha új platform kell (Rust, Go, TypeScript), elég hozzá egy új emitter modult írni.
Tervezési döntések
A constraint-alapú generikusok (<T constraint IEntity>)
miatt a típusbiztonság minden platformon megmarad. Ez nem triviális, mert a Java, a C# és a Python
egészen másképp kezeli a generikusokat; a contract szintjén viszont egyetlen jelentésük van.
Az async kulcsszóból C#-ban Task, TypeScriptben Promise,
Pythonban async def lesz. Azt, hogy egy metódus aszinkron-e, a contract mondja meg,
nem kell platformonként külön eldönteni.
Az import rendszerrel a contractok hivatkozhatnak egymásra. A PolyPersist például az összes store interfészét UniContract definíciókból veszi, ezért a C# és a többi platform implementációja ugyanazt az interfészt valósítja meg.
Hol használom?
A PolyPersist összes cross-platform interfészét a UniContract generálja. A D3I is erre épít, hogy az emitterek kimenete egymással összhangban maradjon. Ahol több platformon kell ugyanazt a contractot megvalósítani, ott ebből generálom a kódot.