 Good afternoon. And welcome for the second session in the Cloud Open. So this session is about modern programming languages for cloud native applications. Now, so we can talk about why cloud native applications are special and what's wrong with existing programming language abstractions like functions, modules, objects. So that's sort of the gist of the talk today. So the first talk was an excellent presentation done by Jim at Solo Inc. He explained running microservices at scale. That is, he addresses the deployment side of microservices of cloud native applications. So this session, I'm going to look at the development aspects of microservices. That's sort of the topic. So I'm Samira Jayaswama. I work for a company called WSO2. I'm a Java developer. I've been writing Java code for about 15 years now. So recently, I joined the Ballerina project, which is an open source project sponsored by WSO2. Ballerina is a new programming language that we are working on. So today, I'll have examples from Java, C sharp, Ballerina, like a bunch of languages to explain what are these modern programming language abstractions. So let's get started. All right. So what are cloud native applications? So this topic is, I'm sure you all are aware of, but I'm just going to have this slide to explain the context here. So this debate is well-established now. What are monoliths? What are microservices-based applications? So I'm not going to spend more time here. But the idea is when you look at a monolithic application, all the code layers, boundaries, software components, they are usually in the same process. And multiple teams are working on this monolithic application. You can see different layers. And each layer has a boundary. And then probably there are several components in a boundary. And each team may be working on that boundary. So there are a lot of complexity in these applications. And their language has evolved to handle this complexity. Now, when you move from a monolithic application to microservice application, the fundamental idea is decomposition. You decompose a monolithic application into smaller components. And these smaller components talk to each other over the network using different protocols like HTTP, GRPC, GraphQL, using different message from adjacent, protobuf, and things like that. So that's the fundamental difference here. So these smaller components, most of the time, they have network interfaces. They talk to other services. And they communicate using plain data. So those are the fundamental differences. And it's funny, people who have started migrating from monolith to microservices, some people are in their journey modernizing their applications. And some people, they have all done it. Now they're coming back to monoliths so that everything is happening here. But I think the co-IDI is sort of settled now. So this is a sample application. I took it from Google microservices demo application. It's not a real-world application. It's an application Google Cloud has developed to showcase their capabilities. This is that application. I'm going to spend a little bit more time here explaining this. I'm going to switch to my... So there are open source, I think. Let me see whether I have it. So this is basically... If you want to look at the code of this sample application, this is the place. You can go check out this example. Here, I'm going to scroll down to show you the application architecture here. So this microservices application has several components. You can see front-end bunch of services talking to databases, RadishCache, different components, and these are their interactions. So you can think of them as network interactions. So this is high-level architecture. This is a hand-drawn diagram. And these services are written in different languages just to showcase Google Cloud capabilities. These are written in different languages. So I basically... Let me find my window. So I rewrote this application with other colleagues using different... Basically, this is rewritten the same concept, but with another set of abstractions. Now, with that... Sorry. Let's look at the architecture of this diagram, of this product. So you can see the same code base, the ad service, cart service, front-end. Everything is the same, same concept, same network interactions. But here, I just visualized the architecture, previously used for the handwritten diagram. Now, this is completely generated from the code. So this image will not usually go out of sync with the code, like it's the code. And it's... So this shows certain interactions between different components. Now, these are service interactions, but you can go one level deeper as in, I want to see what's in each of these services. So this is an HTTP service I'm selecting. And all the internal services are GRPC services. So if I go here, now I'm looking at a little bit complicated image, but essentially what you are seeing here is the details or the interface of each of the services. Now, the first one is an HTTP service. You can see what are the resources that are there. See, it's like visualizing. So you can see certain verbs, HTTP verbs, get, put, pause, delete, and what are the resource names. So when you select on one resource, you can see that this get metadata resource internally talk to cart service and the currency service. So the cart service and currency service, they are GRPC services. So you see a different perspective there. As in RPC, you can see in the cart add item, get cart, sort of methods. In the HTTP world, you look at the world as a resource oriented view. You can see resources and actions on them. So that's HTTP and GRPC difference. Now, this shows individual resources and their interactions. I'm going to go to one level deeper now. If I click on here, I can go to source or design. So design means I clicked on the metadata, get metadata resource. This shows me, this visualizes the code now. So this is exactly the code that is there in that resource. Here, this is the resource code, and it's complicated as in you have iffals, while loops. It's complicated and we don't need these details. But what we are really interested is the network interactions. So if I do this, you can see that my resource connect to two different services, but I can hide all the complexity and just look at the network interactions. And then you will clearly see this resource connect to get the cart service first, and then it connects to the cart service. Now, these visualizations, these are possible because of the abstractions in these languages, right? These are all generated not handwritten diagrams. So we're gonna talk about some of these abstractions today. Let me go back to the slides, all right. Okay, so now, if I focus on one of the components, like in this image, the previous image, one of the components, you will typically see this structure where it's a service. There's a web browser or a client is talking to them. And then that service typically talk to other services. But what's special here is these components work with data all the time. It's all about data. You receive data, maybe via JSON or Protobuf, then you pass data into an application specific type, then you manipulate that, then you send data back to some internal service, so the database. So if you look at any of the small components, this is sort of the high level view that you see, right? There may not be complicated layers in each of these components. That's the pattern that we have seen. Okay, so now how do we write these applications? What are the abstractions that are there in programming languages? So this slide sort of groups these modern abstractions into multiple buckets. So the pattern that I have seen is you can organize them as data abstractions, you can organize them as network abstractions, and you can organize them as concurrency abstractions. So today I'm gonna talk about first two. I don't think I have time for the last one, but let's see how it goes. So my focus is on data and network abstractions today, and we look at how different languages handle these cases. So these are the high level abstractions. Now, how do you add these abstractions into languages? There are two choices I have seen. First, if you look at Java, the good example, like Java is innovating super fast these days. They have six months release cadence, so what they are doing is they are introducing new language features or features like every six months. What they are doing is they are basically modernizing Java into writing modern cloud native applications. So they are basically evolving Java to support modern abstractions, so introduce modern abstractions. I have some slides explaining what are those. Then the other approach that I have seen is people are designing new languages because sometimes these three abstractions, they are sometimes deeply interconnected to each other. So you may not be able to evolve existing languages properly to handle them. So some designers, they are basically creating new languages with deeply connected abstractions that works like seamless development. So let's see. The first one is data abstractions. So data abstractions is how you handle data, how you represent data, how you describe data that are coming over the wire, and how you manipulate. So these are the three aspects. So I'm using some examples from this book. I think you can, if you have the slides, you should be able to look at the link. So this book is object, sorry, data oriented programming. It's a decent book. I read it and I agree with most of the points there, but I have some disagreements as well. So there are some things with the author. That's a different problem. Anyway, so that book says, so data oriented programming is not new. It's been there, people are practicing it. This book has like nice way of arranging the content. So it book says, represent data as data, not data as let's say objects. So because if you think of objects, you have code and data bundled together. That may not be the natural way to represent data. So this book goes on to say, you represent data as data. So when you work with data, it has to feel natural. So let's take a look. And also it says, it talks about plain data. So what do you mean by plain data? So in all of these microservice applications, we work with plain data. It's that what's coming on the wire, what's coming over the wire is plain data. It's a JSON payload, it's a protobuf. There's no behavior associated. There's no meaning associated. I'll get to that point. When you look at the data, you see the structure, but there's no meaning. I'll get to that point. Okay, so the next point is, we're gonna look at WOP, object oriented programming and also data oriented programming and evaluate whether which approach is right for cloud native application development. Now, object oriented programming is great, right? There's no question about it. It worked well for monolithic applications. You have complicated boundaries and it's good at encapsulating things, right? So it worked great and people are still using it. There's no question about it. But now the question is, does it work for cloud native applications? Does it work for smaller components where you don't need to have that complicated layers, complicated boundaries? So that's the topic we're gonna talk about, right? Because when you look at these containers and microservice applications, the layer or the boundary is a network. You have smaller components and their bound is a network. They talk to each other over the network. But within that boundary, what's happening is usually simple. You get some data, you talk to database, or you talk to some other service, you combine, you blend everything into a single response, pass it back, right? Why do we need complicated layers? So whereas data oriented programming talk about representing data as data and keep code that processes data separate from data. So just two different concepts you can pick and choose. You can, if your microservice is complicated, use WP. If it's a simple, use data oriented, there's no silver bullet, right? All right. So data abstractions, there are three, we can look at data abstraction in three different ways. One is how do you describe data? Like what is the application level type that I'm gonna use to describe data? And how easy it is to describe data? Like create data, like data literals, records, open records, openness of a type, the structural aspects of the type. Then the next one is manipulation. How easy it is to manipulate data inside that boundary. There are of course communication, like how easy it is to get some data from the wire and transform that to application type, et cetera. All right. So this is a simple JSON payload, right? As you can see, the bunch of key value pairs, a JSON object, you can interpret this in different ways. So I can say first name, last name. This has to be a person. And there's a book, field name books, and that's an array. That's array has, it's an array of JSON objects again. That can be books. When you look at this, you can clearly see the structure of data. You can see the structure, but what's the meaning? How do you, what's the semantics? What are the semantics of data? How do you interpret it? How do you describe it in programming language? One option is this may be a person who owns these books. I don't know. Maybe it's a library member who borrowed these books. But two different things, but the data is the same. So we can interpret this data in different ways. So that's about describing data. So we use to describe this data in programming languages, we use data types, right? For integer int, like for this, this is a Java way of doing it now. In Java, we used to have these pojos where you write plain all Java objects. You have getters, getters, private fields. That's how we represent the data in Java. But with the new Java innovation, they have something called records, right? You can use Java records and you can represent this tree data structure. So here, we have a member record, last name, first name, and then bunch of books. Then we have a book record, title and author, and then, right? So you can see the association between these records. So this is, in Java, this is the best way to represent data now. And then in the main method here, I'm not sure whether you can see. In the main method, we are creating a data literal. We are creating a record called Kelly with this information, right? So this is Java way of doing it. In Java, everything is an object, so that's why you see, even for records, you have new sort of, sometimes, for some of you, it may not feel that natural, because if you look at the JSON, this is a JSON literal. This is how you see JSON, right? But this is in Java, so it's perfect. It works for Java. So I'm gonna look at a Ballena example now. So this is how you do the same thing in Ballena. So in Ballena, there's something called records. So you have author record, book record, and then member. And on the other side, I'm creating the same, same, let's say, instance of the member record. But when you compare that with that, it looks like the JSON. The only difference is these keys, there's no quotes around it. I'm gonna go back to JSON for a second. So JSON, you have these quotes, and here, there's no quotes. So that's slight difference, but you write the data literal, it's the same. So those are the differences. I can't talk about Kotlin data classes as well, same concept, but I'll stop here. Okay, so this example is about algebraic types in Java. So Java field classes, the idea here is how you represent choices in something. I'll take an example in the same member. So let's say member is a generic concept where you have, let's say, a regular member and a VIP member. Now this is a choice. Now field class means the developer who works on this can define these choices, but no one else can extend it. So that's the idea of Java field classes. I'm sure we are familiar with it. So that gives a nice way to compile time check, like it's sort of closed. You can, at compile time, you can check whether this member is a regular member or a VIP member. Now here they have used, this is, I took this example from the Java article, what it says is it's how you represent JSON in Java. If you look at the JSON specification, you can see that JSON is a union of things. A JSON can be a simple number. JSON can be a string. JSON can be a JSON object. A JSON can be an array of anything, array of integers, array of string, array of JSON object, so it goes on. JSON is something like that. So here, if you look at this, a JSON value is a combination of strings, numbers, nil, boolean, arrays, and objects. So this is how you represent JSON in Java. And in the main method, you're creating a new JSON value, and in this condition, what's happening here is you're checking whether this JSON value has a certain structure. So you're looking at the structure here. Not really, it doesn't matter anything, but it looks at the structure, and then if the structure is okay, inside the if condition, you can access those fields in a type-safe way. So this is how you do that in Java. Let's say how you do that in ballerina. In ballerina, there's a type called JSON. The built-in type defined in the language. So here is that type. Here, I'm defining that data literal. The same thing we have done in here as well. The same thing, but in a different syntax. And now what I'm saying here is I'm looking at the JSON here, and I'm asking whether this JSON value, does it look like the person? Does it look like a person? That's what I'm doing here. JSON from JSON with type, I'm checking whether this JSON value looks like the person type defines here. At runtime, because I don't know what the message I'm getting. So I'm going to simply do a check here and saying, and that can fail. Because sometimes the JSON may not look like the person, so it's going to give me an error. So I had to handle error, so here I'm just printing it. But if the JSON looks like the person, I'm getting the person back. So same thing, now I can access these fields in a type safe way. So same thing, but in two different syntaxes. So the languages are evolving to handle data in a natural way. Like if you look at Kotlin, the same aspect. All right. Now, I took this banana example, but you can see this is pretty common. You can see this behavior in Java, C sharp, like I think first one to introduce it, in mainstream languages. So this is like sort of a declarative way of manipulating and querying data. This is like SQL is doing it. So here what I'm doing here is basically looking at all the countries. I'm getting a list of countries somehow. And I'm iterating the countries and basically filtering out countries with this condition and then creating a local variable, calculating the case fatal ratio using the deaths and cases and sorting it and limiting it to 10 and selecting all the countries. What I'm getting back is a JSON. So that's simple data filtering. Now, this is readable. This is like if you come back after six months, you can still understand this. So this is like certain abstractions that you see. So next one is consuming services as in how easy it is to manipulate or accept data from or send data from network boundaries. Now, here I have defined a type called country. This is sort of the expected structure of data. This is what I expect from the service. Now, in the main function, I create HTTP client to a random URL. And I'm doing a get request here. I'm doing a get request here. And I'm getting back a list of countries. So here I'm not worrying about JSON serialization, JSON passing or anything like that. This, as a developer, I worked at a little bit higher level now. And I describe my expected structure of data. I'm getting a list of countries here. So it's sort of like the data validation as well. And if it fails, it's going to return an error. So this check is doing that because if what I'm getting back is not a list of countries, it will check. Check means in ballerina it just returned from the function. So it's like eliminating the error. If it is countries, I'm going to print it. So that's very simple. So here what I'm doing is, I'm doing two things. I'm describing the data format or data structure, and I'm also using that to validate the response that I'm getting back from the client. We can go one more step here. This is again data validation, but with additional constraints. I'm saying I want this list of countries, but the population has to be greater than this number. Death has to be greater than this number. This is again you are describing the structure of the data, and also you are enforcing a little bit more constraints. So this is like you can see this in Java as well. So that's one thing. So I was talking about mostly data abstractions, and what that means is now all this comes down to, for me as a developer, I don't have to deal with a lot of boilerplate. I can think in terms of these simple abstractions. I can write my code, because when you look at the smaller components, most of the cases, it's not complicated. You don't have to deal with a lot of things, and it depends on the service as well. So the idea is abstractions as a developer helped me to think in terms of high-level concepts. I don't have to go into the details, JSON passing and all that. So that's one advantage. So the next one here is sort of network abstractions. I talk about data abstractions. Let's look at network abstractions. They are deeply connected to each other. So here, if you look at all the network libraries, HTTP client, HTTP service libraries in all the languages, what you see is similar abstractions in all of the modern frameworks and languages. So you see a concept called listener. Listener is listening, let's say on the port. Listener can be like different types. It can be HTTP listener, GraphQL, GRPC. It's a different concept. Now listener basically accepts request, and then based on the nature of the request, it's the path, it's a method named GRPC. It dispatches two services. A listener can handle multiple services, of course. If it's HTTP listener, then it depends on the base path and the resource signature. So inside the service, usually you manipulate data, and then you talk to a backend. It could be a database, it could be another service. Now, the abstraction that I'm seeing here is something called client. This is pretty standard, like the other term is stub, right? You create a stub. Essentially, what I see is that as an abstraction, because as a service developer, I don't directly go to the service. I talk to the service via this client abstraction. Now service and client communication, I can classify them as network interactions. So if you think of that way, the service and client interactions, you can think of them as network abstractions, network interactions, and if you look at it that way, then your network interactions are explicit. And then as a developer, I know I'm interacting with the network, and I know this network calls can fail. I have to think of error handling as well explicitly. So the things change. We'll look at those examples as well. So these are the high-level concepts that I see in dealing with network boundaries. But if you look at this chain, everything that flows through is data. That's why describing and manipulation data is important. All right. So this is a bit about services. Just services. I think I explained this in the demo as well. So services can be of different types. HTTP services, you have sort of a resource-oriented view. You have sort of resources like metadata, cart. There's one exception here, set currency. I really don't like that name. It has to be currency. Like you are posting, you're changing something. Set currency is like, I would write this as currency. Anyway, so what the idea is, you have these actions and resources, right? And in the GRPC world, it's sort of RPC. So what you see is always remote methods. So different kinds of services and different ways of thinking about them. I'll get to those in a second. So this example is from ASP.NET minimal APIs. So here, this is an HTTP service. And it manages to-do list, right? It's simple. So as you can see, for each resource path, you can attach a handler, a function, right? This function basically, when this service receives a request, let's say to-do item by ID, this handler will invoke. And here, what it does is, talk to database, see whether there exists ID with the database. If there is, return an HTTP OK response. If not, 404, like not found. Now, this is explicit. I mean, this is explicit in the code. The advantage here is, now, this code, this is a service, you can easily generate an open API description for this, right? So, I mean, that's why when I read the .NET, it says you have to use, like, it's best practice to use type results so that the open API generation works because you are pretty precisely saying that this is the resource path. You can invoke it. If you invoke it, these are the results. So the open API generation tools, they look at the code and figure out this has to be the service, right? So that works. Now, this is, like, how we do the same thing in Ballerina. So in Ballerina is a new language. So, example of an existing language and adapting to services. This is a new language. How do you decide a new language? So here, service is a concept, a first-class citizen in the language, just like function and classes. The service is a first-class concept and a service can have, service can get attached to a listener. Here, this, I'm using HTTP listener. So this has to be an HTTP service. So the structure here is a service can have multiple resources. Resource function get to do. And remember the HTTP resource-oriented view? The way it is designed in Ballerina is using this resource function concept and you have action as a separate and the path as a separate. So there are different ways of doing things here. I'm just... So here, if you look at this resource, path, we have an ID, and it returns a to-do element or not found. Now, if you look at all the resource signatures, you can see the behavior of the service. When you invoke a resource function, you can precisely see what you get. Similar, sorry, similar to GotNet. The same thing. This is... It's evident in the signature. So you can generate open API descriptions. And if you have an open API description, you can get the source code as well. So these things allow you to do that. Now, the next one is sort of the visualization of the service as a SoGaEdit editor. So this sort of coming from the VS Code tooling, if you write the service, you can visualize it. And you can see what's happening. This is another view of the open API description. All right. So the same thing. Now, the GRPC example. This is GRPC in C sharp. Unfortunately, I don't have the prototype, but it's the same standard prototype that they are using to explain GRPC. There's one method, say hello. It accept the hello request, hello reply, pretty standard. And in Ballerina example, it's the same. You have a service. Now, in this time, we have a GRPC listener instead of HTTP. Now, we have... Earlier, we had remote resource functions. Now we have remote functions. So in Ballerina the way... You can think of services being a collection of resource functions or a collection of remote functions, depending on the protocol. How they have designed it. All right. So these abstractions... I'm not going to spend more time here. So these abstractions... These are the advantages of having high-level abstractions as a developer. So I get the data civilization, desalization automatic, and that is more common nowadays. And data validation at the boundary. Before I work with data, I need to know the data I get is correct. So data validation at the boundary is that. So you can have... You can describe the data. You can have additional constraints. And in your business logic, it's safe to work with data. So you have guaranteed that. Otherwise, you refuse to accept requests. And then bidirectional mapping between IDL to code. The IDL is, I think, interface description language. It depends on the protocol. If it's GRPC, if it's GraphQL, it's GraphQL schema, I guess. If it's HTTP, if it's OpenAPI. So what these abstractions help you do is, if you have an IDL, you can generate the service. If you have the service, you can also generate an IDL. So you can have a code first, contracts first. It doesn't matter. The other thing is network observability code to cloud means... When you write the service, these abstractions help you to generate Docker files, Kubernetes, HTML files, everything. There are samples around. I'm not going to cover that. Obviously, the application architecture. What you saw earlier, the demo, how you visualize the application, it's all based on the abstractions. And it's visible in the code. That's how a developer think of that application. Yeah. So client abstraction is the same. Helps you to... I have some examples in the client as well. So it helps you to work so you can think of explicit network errors and things like that. So this is an example written in Ballerina. So the idea is it first create a GitHub HTTP client and then it does a get request to this resource repository pools headers by passing headers. You get a list of PRs back. Then it creates a client to the Google Sheets API. This is a generated client. And then it dumps all PRs into as rows. That's simple integration. Right? Now, if you see here from GitHub, what you get is a list of PRs. Check means there are network failures. There are data validation failures and things like that. So you're working at a little bit higher level than you used to be. And then as you see here, this arrow here indicates it's a network. So this GitHub is a client abstraction. And this arrow means you are doing a network interaction. So ideally this GitHub is an object in my same process. Ideally, they are not interactions but this abstraction and this client is like a proxy to the backend service. So it helps you to think that you're explicitly doing network interactions. You have to handle errors. Here I'm not handling errors, I'm just passing. But of course I can handle the error. So with that, I can easily visualize this code as a sequence diagram. Because when I, let me go back to the code, here I know that this is a client. I know that this is a network interaction. This is a client. And then I can see this is a network interaction, this is a network interaction. So when you look at this image, you can see there are, you can see the GitHub service, you can see the GSheet service and multiple network interactions. So this is all visualized from the code, not a handwritten diagram. This is what abstraction, sometimes you can come back to code and you can see what's happening. All right. So, so that brings me to the summary. So at the beginning I talked about data and network and concurrency. I don't think I have almost 40 minutes on time, but the concurrency abstractions, like I'll explain briefly. It's another talk. So we have this lightweight threads that are coming in Java and Go has already done that. Now, Balana is also like lightweight threads. It helps you to write structured concurrent code. As in, it's not like, let's say you can compare Async-avait versus writing concurrent code as blocking code. So that there's different, if you're interested, read about structured concurrency. The other one is concurrency safety. These abstractions, now some of these languages helps you to detect concurrency-related errors at compile time. Concurrency safety at compile time. Most of the cases you can do. So that will be another topic. So I guess I'll stop at that point. So thank you for joining. Thank you, everyone. Thank you. Yeah. If you model these as objects, then I can decide which fields are private, which fields are accessible like that. Right. So that's a fair point. Maybe some use cases, it's valid. But in some use cases, the way I have seen is another way to, let's say, implement your scenario is let's say this is an example. For some functions or some areas, you don't want to expose books. You want to give just the first name, last name, right? But what you have is this, let's say, this case, member. So what I have seen is you can create now, if you look at the structure of the data, there's this concept called open records. I think I have this here, this one. Data representation, open records in the middle. The reason is when you create these records, I'll go to the balna because the question is asked, these records are opened by default. When I create data literals, I have to have all the four fields, but you can have additional fields. The way I have implemented a similar example, the way I have done it, I have another member, let's say member without books, where I don't have books fields in that case, but I have only these ones. So what I do is from that member, I basically assign that member to the member without books and pass it to the other functions. So it is let's say if you are thinking about complicated application with layers, that can be a problem because then you have to manage all the types. But I have seen, even in that Google microservices demo, in some of the services there are a bunch of types for various parts and you convert your data into that type and pass it back. That's the way of hiding in the data oriented programming. You always look at the data in a different lens and then what you call it like projection, right? You have the full data and then you create another type by selecting the fields that you need. So that's a good point. Thank you. Thank you.