parigot: the operating environment for microservices

For developers

parigot is both an operating environment and a development tool. parigot offers you the ability to develop, test, and debug your microservice apps as a monolith and then deploy them as (normal) microservices. You can probably see what this means:

  • Reproducible test results
  • Well-defined startup ordering
  • Intra-service dependency resolution (including detecting cycles)
  • Debuggers work because in development your "app" is a single process
  • Unified logging, not one log per service

How does it work?

Fundamentally, parigot works because it has a different programming model. The programming model allows parigot's infrastructure to make significant assumptions about how the program works. For example, there is no visiblity or access to the "raw" network... parigot's model only allows calls to other services. (Unless you build your own services that offer this capability!) Thus, the infrastructure can make assumptions about how the program works (e.g. listening on sockets) and can then take different actions than a typical development model. A typical development model would mean that the infrastructure cannot make assumptions about what ports are in use, which ones are connections to other services in the app, etc.

What languages do you support writing parigot programs in?

In principle, all of them. parigot's infrastructure is built using Web Assembly (or wasm-based containers) so any language that compile to WASM will work.

Each language, however, requires some glue code to hook up to parigot's ABI; this is exactly analagous to various languages that need to either use or replicate the C language library libc.a for linux. This library, in both the parigot and linux case, effectively sets up requests for the kernel by laying out some bytes in memory and then requesting a kernel service. The kernel looks at the bytes (the parameters), performs the action, and then writes some bytes back into memory that are the result. We have support for golang already working and python is next.

How do we make money

When the software is open source, of course folks ask this question a great deal. We are developing a deploy and hosting service that will run parigot-based programs at a significantly lower price than any other cloud provider. So, a parigot program can be developed on your local laptop in the "easy way" as a single process and then deployed into to the cloud as discrete services that talk to each other over the network.

You can still parigot yourself, with your own programs and services, without deploying to our service.

What does programming model actually look like?

Here's a snippet of a simple program, written in go:

func main() {

lib.FlagParseCreateEnv()

if _, err := callImpl.Require1("log", "LogService"); err != nil {
    panic("unable to require log service: " + err.Error())
}
if _, err := callImpl.Require1("file", "FileService"); err != nil {
	panic("unable to require file service:" + err.Error())
}
// This code is the *implementation* of the StoreService.
if _, err := callImpl.Export1("demo.vvv", "StoreService"); err != nil {
	panic("unable to export demo.vvv: " + err.Error())
}
store.RunStoreService(&myStoreServer{})
//...

}

// Ready is a check, if this returns false the library should abort and not attempt to run this service.
// Normally, this is used to block using the lib.Run() call.  This call will wait until all the required
// services are ready.

func (m *myStoreServer) Ready() bool {

if _, err := callImpl.Run(&pbsys.RunRequest{Wait: true}); err != nil {
	print("ready: error in attempt to signal Run: ", err.Error(), "\n")
	return false
}

logger, err := log.LocateLogService()
if err != nil {
	print("ERROR trying to create log client: ", err.Error(), "\n")
	return false
}
m.logger = logger

fClient, err := file.LocateFileService(logger)
if err != nil {
	print("ERROR trying to create fs client: ", err.Error(), "\n")
	return false
}
// ..

}

Discussion

Some things to note from this example:

  • You have explicitly require and export services you depend on or provide, respectively.
  • parigot can build a model of everything that is needed to run your program--and maybe more importantly what is not needed. If your system has 300 services in production, it is unlikely you need all 300 to run your program and parigot can compute what is needed exactly.
  • Secondly, this give parigot a dependency ordering. In other words, we make sure that your code will not start until everything it requires is up and ready. If there is no deterministic startup order, parigot will abort.
  • The function `Ready()` is called to inform your program that your dependencies are prepared. You then can call LocateSOMETHING() to get an interface that is the implementation of that service. Note that you cannot tell if that service is in the same address space or over the network!