Transcript
Reisz: We're going to be chatting about microservices. More specifically, we're going to be answering the question, are they still worth it?
I want to recap our journey to microservices today. As early as 2005, Peter Rogers first introduced this term called micro web services. 2005, during a presentation at the Web Services Edge conference. At this conference, he went against the conventional thinking at the time. Think about 2005. This was the height of the SOAP era, service oriented architecture. This is the height of that curve. He was arguing for RESTful services. He described in this presentation how well designed micro web services platform applies the underlying architectural principles of the web, and REST services together in a Unix-like scheduling and pipelines to provide radical flexibility and improve simplicity in service oriented architectures. This is 2005.
Fast forward to 2011, with 6 years of people innovating and thinking about this problem. At a workshop for software architects held near Venice in May, the term microservice was used to describe what participants were actually seeing and practicing in practice. In May 2012, the same group came together. Microservices was the term that was officially embraced by this group, at least to describe this architectural style. James Lewis who was there presented on some of these ideas and case studies that followed on in March 2012. We're talking 8 years ago, at a conference called 33rd Degree in Krakow, as did Fred George. Adrian Cockcroft was actively developing these systems at Netflix and described these as fine-grained service oriented architecture. Some of those very first presentations, even before the name microservices came about, happened at QCon. You can find those on InfoQ. Other thought leaders in this space were people like Sam Newman, at this time, were people like Evan Bottcher, Martin Fowler, of course, Graham Tackley, and many more. These are some of the early thought leaders of microservices.
Microservices truly found its stride with the introduction of DevOps. It has become more popular for just how we build systems, particularly with continuously deployed systems today. However, that they do come at a certain cost. That's what we're going to focus on. We're going to zero in on some of the costs. We're going to talk about and try to answer the question, are they really still worth it? Yes. If you're solving the right problem, they're still worth it.
Background
I'm going to let you introduce yourselves and tell me a little bit about your lens on how you see microservices today. Nicky, you deal with lots of services. Introduce yourself and talk a bit about it.
Wrightson: I'm Nicky. I'm a Principal Engineer at Skyscanner. I got introduced to microservices about 5 years ago, at the "Financial Times" where we were redeveloping a content platform in this microservice orientated architecture. Try not to decompose what we already had, but trying to bring up something fresh and new. We were really trying to take on best practices there in terms of not only the architecture, but also the resilience, the DevOps side, and all of that. Since then, I've gone through a couple of roles, but it's landed me in the big data world where microservices are viewed in a different way. There's less of the number of distinct microservices, more on you are scaling horizontally. It gives me a different lens on it from that angle too. That's my background.
Reisz: Yan, you come from a serverless world. Tell us a bit about your journey to microservices and introduce yourself.
Cui: My name is Yan Cui. I've actually been working with AWS for over 10 years now. Started with just running stuff on EC2. As you can imagine, pretty monolithic systems, and then discovered microservices about 7 or 8 years ago. I did a whole migration to microservices, running on EC2. Then when containers came around, the type of containerization, worked there. Then eventually got to using serverless. Nowadays, I pretty much predominantly use Lambda and serverless components for everything I build, occasionally jumping into ECS, if I need to. Certainly, similar to what Nicky you just said, I don't think microservices, there's one way to look at it. Certainly, with Lambda, you see a lot of very small components that shouldn't be considered as a microservice on their own. They function as what you would describe as microservices in that they are independently deployable, independently scalable, and they can fare in isolation as well. Certainly, when I think about microservices, I'm thinking more in terms of the scope or maybe what you've described as the bounded context, in DDD terms. When I think about microservices, that's probably the first thing I think about, as opposed to the actual infrastructure components that are deployed.
Definition of Microservices
Reisz: I'm trying to remember the definition by Adrian Cockcroft. One of my favorite definitions talks about finely-grained service oriented architecture in bounded context. How do each of you define microservices?
Wrightson: I think this one I've struggled with over the years, because I didn't actually understand anything to do with DDD for the first 3 years I was working in microservices. Nor did our architects, which led me to a very specific, "The smallest most independently deployable, as Yan was saying, piece of the code." It was wrong. I like to bring it back to that bounded context idea. It's not the same idea, but it's certainly talking about that context in that domain, and not having dependencies out. It's completely isolated.
Cui: My view of microservice is very similar, is around bounded context. In fact, often I find myself using microservice and bounded context interchangeably. Certainly, I think, when you're looking at lambda, they tick all the boxes of what you have considered as microservices, from an infrastructure point of view. One function doesn't do everything, is the one small piece of a bigger machine that have to work together, everything has to work together in perfect synchrony for something to actually happen. The thing that actually happens is what you actually want from the business point of view. Again, when I think about microservices, I think about a scope of responsibilities that are self-contained. That within that scope, one team or one person can own everything, and can consider, and can move, can reorganize things however they like. Outside of that, everything should be done via messages. It should be as little direct dependencies as possible because otherwise you create those hard couplings that create cascade failures in more complicated environments.
Where Microservices Are, On the Adoption Curve
Reisz: I did a little introduction towards microservices. Literally, we're talking 8 years, where these things were spun up. If you think of the technology adoption curve, technology, early adopters, early majority, late majority, laggards, where on the adoption curve do you think microservices are today?
Wrightson: Surprisingly, I was actually thinking that we're not as far along as I always expect. We've jumped over that chasm. There is a lot of early adopters out there. I think we're going to struggle still with rest of it. It's not because of microservices. It's all that goes along with it. You need actual fundamental organizational shifts and mind shifts. You've got a lot of organizations out there that are still on-prem. They're not even thinking about the cloud and the technologies that go along with that. They're not thinking about DevOps and continuous delivery. One more thing about microservices, it solves an organizational problem. It doesn't solve a technical problem. You've got to manage to be able to fold in that organizational shift before you even go down this road. That's what I feel stops late adopters, because you've got so many people out there running very complex monoliths in very traditional organizations.
Cui: I would say we are still in the early majority phase as well. Certainly, I work with a lot of customers who are going straight from that on-premises or at least monolithic systems, moving to serverless, and having to learn about microservices, how to decompose the problem domain. Also, all the challenges that comes with microservices from the monitoring, debugging, observability angle. Certainly, there's a lot more of those companies out there than I probably thought a few years ago, until I started working with more companies from all walks of life. Then realized that actually not everyone's doing microservices yet.
Reisz: We get into this echo chamber where we talk to each other, we go to conferences, and we think everybody's here.
Cui: Yes. Conferences talking about microservices is just the norm.
Where the Microservices Bar Is Today, and What's Needed to Operate Microservices Successfully
Reisz: Absolutely. Nicky, you hit on organizational. Let's talk a bit about this. I remember when I was first really trying to grok and understand the idea of microservices, finding one of this blog by Martin Fowler. I remember there was this picture, it just vividly sticks in my head that said, "You must be this tall to be able to operate or run microservices." Where's that bar today? What do you need to do to be able to operate microservices today successfully? And then second, is it higher than it was or is it lower than it was?
Wrightson: I think what I was alluding to earlier is that, organizationally, we have microservices so that we can deploy discrete changes quite rapidly, that turnaround. The point that you need to have that, and that becomes an issue is when your company has grown to a certain size and you want to have teams having that autonomy and people having that autonomy, even more granular. What size organization? That still sticks. I think actually in some ways, it's got more complex, the landscape. For every complex problem, there's been a simple solution rolled out, actually, that's gone live. There's been 12 simple solutions rolled out for that complex problem. You then have to understand all of those 12 products to be able to try to solve this problem. It goes on and on. We're putting a lot of tooling out there. We're making the amount that you have to understand about your landscape much greater. I think it's still hovering in a similar place. The breadth is going slightly wider on what you need to look at.
Reisz: Yes, absolutely. Makes sense.
Cui: I'd like to echo what Nicky said about the tooling. I'm certainly not a fan of where the DevOps ecosystem in terms of tooling are and how obsessed everyone is about which tool to use. That really bugs me to hell. The other day I saw a promo for KubeCon, and there was one-and-half minutes introducing every single tool you got to use in your Kubernetes cluster, from monitoring, from controlling, and all the options.
Reisz: The options, and choices you got to make.
Cui: Yes, exactly. I think a lot of the technology that's come along, that's supposed to help you build microservices architectures has actually made it a lot more complicated. The learning curve has gotten a lot deeper. At the same time I also think that we are building more complex systems as well. Especially, in the serverless world, I see a lot of very interesting and also very complex, event-driven systems which brings in a lot of challenges, that even API centric microservices might not have to solve in terms of tracing in locations across all kinds of things like SNS, Kinesis, and DynamoDB streams, which are important. There are tools that are getting better to do that now. It's still, I think, certainly from the observability side of things is more interesting now, because the people are building more complex solutions to more complex problems.
Reisz: Leif, introduce yourself.
Basically, I was recalling back to one of the very first blog posts that I remember reading on microservices from Martin Fowler. He was talking about you needed to be this high. He was talking about being able to observe and log and monitor systems and organizational maturity, things along those lines. The question is, today, where's the bar with microservices? How high? How tall do you need to be?
Beaton: How tall do you want to be? There is a cost of purchase to microservices, in terms of there's a fundamentally different mindset about developing applications from a microservice perspective, versus the old monolithic process. Things need to be a little bit more clearly defined up front than what you can do with a monolith. As a small startup, you can start up with a monolithic approach and see where it leads you. With microservices approaches, that tends to become a little bit of a [inaudible 00:16:52]. That's not to say that you have to be a conglomerate in order to avail off microservices. As long as you do the planning upfront and decide this is the scope for this part of the application, and this is the scope for that, then there are benefits ranging from the smallest of companies to the largest one. I hasten to add, that there are certain applications that should stay monolithic, though.
Challenges with Microservices Today
Reisz: As we're talking about this, you started to hit on some of the problems with microservices. One of which, as Nicky says, complexity. I have this in vision of the CNCF landscape, it is a monster. There are so many different possibility of things that are out there. Just take CNI with Kubernetes, you've got Canal, Flannel, you've got Weave, Cilium. You've got all these choices on how you might do container networking. Complexity is obviously one of the things that definitely exists in microservices. What are some of the other challenges with microservices today?
Beaton: Overall governance is one. You touched upon something a little while ago about monitoring, and tracing, and stuff of that nature. That's, of course, critically important when you start moving into this, because communication throughout the application is not a function call with in-memory and run on the same application. It's a network call, which may or may not be locally on the system that you're talking to at the moment. Being able to monitor, track, and trace communications throughout an application is absolutely critical. Without that, you're essentially trying to find a black cat in a dark room while wearing a blindfold.
Why Organizational Maturity Is a Challenge with Microservices
Reisz: Sure. Nicky, you talked about organizational maturity. Why is organizational maturity one of the challenges with microservices?
Wrightson: It drives, I think, being autonomous as a team and being able to take ownership. That I think is needed because you need to take the operations of that set of microservices or your domain as well. You need to be able to do that. Organizationally, means you can own the purpose of that domain. Microservices means that you don't need to ask the payments team if you can deploy it, because you no longer have that dependencies. It's quite a hard thing, culturally, to do.
Challenges around Orchestration with Services
Reisz: Absolutely. Yan, you and I talked a bit recently about orchestration, about how when you're a microservice environment, some of the challenges on making things talk together. Can you talk a bit about some of the challenges around orchestration with services?
Cui: Yes, certainly. I think that our conversation was around how to implement the business workflows using orchestration versus choreography, where the choreography is the name of the game, especially in the serverless world where event driven has become almost the norm. Certain problems are just not worth some of the cost that you have with event-driven architectures where all the tracing, all the monitoring becomes a bit more difficult. There's no central point where you can say, that's how the workflow works, because it's never source controlled anywhere. These all just exist in someone's mental model of how a system works. Certainly, within microservices, I think when you're inside a bounded context of one microservice, orchestration is absolutely fine, using tools like Step Functions that gives you the orchestration engine that solve a lot of problems and make a certain class of problems very easy to implement, and monitor, and debug, and trace. Any business critical workflows, I prefer to use something like Step Functions to capture the actual workflow itself, and make sure that's source controlled and everything.
How Event-Driven Architectures Are Related to Microservices
Reisz: Let's go a little bit deeper here. Maria asked a question in the chat specifically about event-driven architectures. The question is, how are event-driven architectures related to microservices? Can you take one step back now and talk a little bit more about event-driven and microservices?
Cui: Maybe in the early days, when people think about microservices, I think they're thinking about one service calling another one via some RESTful API, and then waits for a response. That's very much the request-response pattern. Then we very soon realize that, actually, there's some problems you've got, because you're waiting for something to respond, and you've got things down, then what can you do? Chances are, you probably are going to go down yourself. You have to do a lot of other things to prevent those cascading failures. Also, there's a lot of things that just doesn't need to happen synchronously. Event-driven systems takes a lot of that synchronous processes and try to make them drive off of some event bus, or some event queue instead. Instead of me doing my thing, I just process an order. Now I have to tell the promo code system to generate a promo code, and then some other system to do their thing. Instead, I just publish the event to some centralized event bus that says, "An order has been processed successfully." Then the payment system, or the promo code system can listen to the event that they care about, then they can do their own thing.
In terms of microservices and event-driven architectures, is a very good mix, because we talk about microservices being self-sufficient, being self-contained, and you have to constantly react to your other API calls from other systems, or you have to call other API systems, then it creates those tighter coupling with other systems. Whereas, if everyone just listens to events, and publish events for things that you've done, then everyone can be much more self-contained and much more loosely coupled with each other. It's a great way for you to build microservices, especially in terms of the communication between different services.
How to Debug, Trace, and Make Sense of a Request Going Through an Event-Driven System
Reisz: Absolutely. Nicky, in your talk, you showed this picture of your organization that showed all the microservices that are there. It pretty much went the whole length of the screen, and there were little dots in there. In what Yan just described, how do you debug? How do you trace? How do you make sense of a request going through a system that's event driven like this?
Wrightson: I don't know why somebody decided to generate that diagram, but I keep using it. It is not as bad as the crazy Monzo or Netflix one that always comes up.
Reisz: The Deathstars, yes.
Wrightson: Exactly. Yes, there often is. We do. We have a completely centralized logging platform, so that allows a certain amount of all of our logs coming in. We have tracing throughout. We have, to a degree, on at least our really important services, we have a lot of SLOs defined for them, and SLAs. Those are monitored. We try not to do these crazy end-to-end tests because you're just never going to do it. You don't know what's working through anything and you lose that interoperability that Yan was talking about a little bit there. Yes, we fall back to monitoring, logging, and tracing. It works some of the time. We find holes in it and we improve it. That's an ongoing daily process. As it gets larger and larger, it is also rolling out to 10 regions for some of our capabilities. It becomes really hard to reason about. We really have to rely on what's being outputted and where our metrics are going as well.
With All the Microservice Problems, Choices, and Complexity, Should Monoliths be Built Instead?
Reisz: We've talked about some of the problems, organizational, complexity. We've talked about problems around choreography and orchestration when it comes to services. Given all of these problems, given all the choice and all the complexity that's there, our microservices work with, should we just be building monoliths?
Beaton: There are monoliths that should stay monoliths, that shouldn't be decomposed and exploded out into microservices. That being said, there's a tremendous array of benefits from going a microservices approach, ranging from the obvious, having more control over what a specific component is doing, a more narrow scope of what a specific component is doing. Quality control over that becomes so much easier. Quality control over the overall application becomes easier too because when you update the search system or the payment system or whatever, you don't have to do a full quality assurance on everything. You just need to focus your efforts on the component in question. There is also less tangible or perhaps less obvious ones. The freedom of choosing whatever technology or language is appropriate for the problem at hand is brilliant. It allows you to mix and match technologies to a much greater extent than would ever be conceivable in a monolithic world. That of course also means that sourcing talent becomes less of a challenge, because if you have two equivalent tools or languages for a specific problem, you double your portfolio of developers.
Wrightson: I think there is no right or wrong answer on this one. I don't think a company needs to be one thing or another. They can actually have different areas of the business operating in different manners that suit them. It comes back to that organizational factor. Can you deploy rapidly? Can you iterate rapidly? Because sometimes you can end up having a beautiful microservices world and not being able to do that continuous deployment and continuous delivery that you're actually aiming for. Often, that's when you think, actually, I could do it quicker with a monolithic application. As long as you've got those contracts between, and definitely leveraging messaging as much as possible. You can have monoliths living with microservices. I think you need to be very thoughtful about what your expectation is, of going to a microservices world. I think people like to get on the new thing. I think that I've seen people jump in with both feet into the deep end of microservices and the teams end up spending 95% of their time trying to wrangle infrastructure, and 5% of the time developing features. It can flip flop easily.
Reisz: I think Sam Newman said something along the lines of, "Build monoliths until you can't." When there's a reason that you need to move beyond it, then start to do that. Aside from that, build monoliths. You don't need to introduce complexity for no reason.
Yan, what about you, what are your thoughts?
Cui: We talk about the right tool for the job. Microservices, all these other things, that they're just a tool. Every tool has got some trade-offs, some pros and cons. You got to weigh off your con specs and see whether or not the cons are worth the while. If you're a really large company and want to move fast, you want to reduce the amount of failures one team can impose on other teams. Then microservice is probably a good fit for you so that you can isolate the team so that one service going down is not going to bring down your whole empire. Or, to be able to allow more people to work on the system, and be isolated from each other.
If you're a staff with two people working on a system, you probably even shouldn't think about microservices, not for a very long time. Just do everything monolithically and deploy everything monolithically until you need to scale the organization. I think Nicky said at the start that microservices allows you to solve organizational problems, not technical problems. I've heard people tell me a lot in the past that if you need to scale you have to use microservices, which is just not the case. Look at something like Supercell, every game they make out there is going to be 100 million daily active users. It's all running off of one JAR. It's all one service, one JAR. You can absolutely scale monoliths. In fact, if you don't know how to scale a monolith, you're not going to know how to scale microservices. For the right context, microservices is a great tool.
Modular Monoliths and Middle Grounds
Reisz: Absolutely. Let's transition over and answer some of the questions from the audience. You just touched on one there, Yan. We talk about almost absolutes, microservices, and monoliths, but there are middle grounds here. Fernando asked a question about modular monoliths. Modular monoliths are JARs, for example, where you have multiple JARs that you might pull together. Yan, you just talked on. What are your thoughts on a middle ground, like modular monoliths? Is this just a natural evolution or do we just go straight to microservices?
Cui: I used to build a lot of pretty big back-ends for games. We have lots of users. It was all monolith, but at the same time is very well modulated in the core level. What we're talking about here is really modularization. You can get modularization at so many different levels. You don't have to have separate deployable, let's say, especially if everything has to get changed together. We talk about microservices, but if every time you change something, you have to change all three of them, and deploy all three of them at the same time, then they're not really independent anyway. I certainly think you don't have to go all the way to the purest version of microservices, where there's absolutely no shared database. Everything is independently deployable, scalable, and all of that. Certainly, I think, practically, there's a lot of hurdles you got to jump through. Personally, I'm very much in favor of middle grounds, at least for now. Maybe as long as you understand the characteristics you want to achieve for your architecture, and what's the best way to get there? How you get there. Maybe it's one step or maybe it's through some middle ground first. You have to make that very pragmatic decision. Otherwise, you can put yourself into a real rabbit hole without getting anything out of your technical decisions.
Wrightson: I totally believe in pragmatism running the design. I think we lose track of that sometimes in our desire to do beautiful engineering work. I'm seeing actually people that don't recognize that they are writing microservices, because there's a whole breed of people that have come out of university that are way younger than me that don't realize that they're writing microservices when they're doing these complex distributed systems. The way that they're writing them is either they're starting off with quite a modular baby monolith, a macro service, and then they're pulling it apart when it starts to become unwieldy. There is that side of things. I've also seen these attempts at modular monoliths, but their trouble was the PRs were stacking up, so the time to release cycle was about a month, which you've now defeated both objects. You've ended up with the worst of both worlds.
How to Deal With Milliseconds and a Finite Amount of Time to Scale in the Microservices World
Reisz: It's what Yan was talking about, "Use the right tool for the right job." In that particular scenario, that was the right tool. Kevin asked a question. They get lots of spiky traffic. When you only have just milliseconds and just a very finite amount of time to scale, how do you really deal with this in a microservices world?
Beaton: That's the Holy Grail right there. Ultra-low latency is what we're all trying to achieve these days. Of course, our customer ranges from healthcare, to FinTech, to trading, to all these types of industries where ultra-low latency is absolutely critical. How to combat that in terms of things spinning up and going down? You need a data plane that is very configurable, and has a lot of intelligence built into it, in terms of being able to determine not only that a system is up and running but the application code within that system is responding both predictably and also timely, and phase it out if such is not the case. Having a data plane that is very flexible, is critical, if you want to start mixing and matching between technologies as well, which we've been talking about with love for power. Having monoliths coexisting with microservices, perhaps introduce a triangular pattern and explode out some microservices to take over certain responsibilities from a monolith, and all these things.
One of our principal focus within NGINX has been that we don't want to make decisions for our users. We don't want to tell them that this is the approach you should be using, and that is the end of it. We want to keep options open and make sure that everybody is catered for. You can define your application in exactly the way you want. You can define it with as much or as little complexity as you want. You want to go monolithic, fine. Microservices, fine. Anywhere in between, absolutely.
How to Deal With Latency
Reisz: Nicky, Skyscanner uses thousands of microservices. How do you deal with latency in hence of this question?
Wrightson: Latency, we balance over different areas. When we're seeing one that's going a little bit wrong, we can send it quickly to another area. We're moving over to Kubernetes. We were on ECS prior to that. Trying to scale up quickly is just not easy. We try to make sure that we've got enough resilience in the overall picture to be able to take the load. We're constantly doing load tests against all of our stuff as well. We work in the travel industry, so you see a very weird spike. We have a very strange traffic pattern going on. It is very different from when I worked in content, but trying to constantly have load tests against that gives us some good understanding. We do have resilience in other regions.
Does the DRY (Don't Repeat Yourself) Principle Fall Apart in Microservices?
Reisz: Yan, "The Pragmatic Programmer" famously made the DRY principle one of the philosophies of software, don't repeat yourself. Does that fall apart when we start talking microservices?
Cui: That's it? There are certain things you do repeat yourself, but starting it off, configurations and things that you end up doing over and over. There's a lot of things that you can modularize into reusable components, both from the architecture point of view, but also from the code point of view. I'm not sure. There's some stuff that you repeat yourself. At the same time, I also don't think that duplication is the biggest problem that our teams face. A lot of teams spend a lot of effort not to write the same code twice, but then they often have much bigger problems to deal with.
Smells to Justify Going Back To a Monolith
Reisz: I want to look for some lessons and principles and things for people to take away. One question that I have is, we've seen these talks, and we've read papers and things about people decomposing the monolith and saying these are the things like velocity, where teams are stepping on each other. These are the smells and things that lead us to decompose a monolith and go to microservices. We're seeing Istiod, for example, going back to more of a monolithic style architecture. What are some of the smells that you all maybe have seen that maybe it's time to step back and start going the other way, and start looking at a monolith?
Wrightson: Yes. No, when I was at the FT, we actually took a load of our services and meshed them up together just because, one, maybe we hadn't got DDD right. That might have been one thing. I think what I've seen elsewhere as well, it's normally the toil level of just being able to roll out any form of change across these. If you've got one team supporting 89 services, because you keep rolling out features and each of those has a security patch. You end up spending more time rolling out security patches than actually running business. I think that's a real smell is when you're spending more time maintaining the code than building new features. I think you need to rethink things. That's the biggest smell for me.
Beaton: I just want to re-emphasize something Nicky mentioned earlier, which is one of the key components I hear of doing it wrong, so-to-speak. This whole, it's new and shiny, therefore, it must be better. That's not always the case. As a matter of fact, that's almost hardly ever the case. It is sometimes, but generally speaking, you need to have a reason to adopt a new technology. Not just that it's new. That's not good enough. If it solves a problem for you, sure, I'm on board with that. If you're adopting it simply because it's Version 2.0 of whatever, then, yes, that's not good enough.
Wrap-up and Final Thoughts
Reisz: Let's go ahead and wrap and just final thoughts. I think Leif started us there. It's just new and shiny, that's probably not good enough. We need to be solving real problems. Nicky, final thoughts to leave the audience with?
Wrightson: I don't think that is an all or nothing answer to this. There's going to be cases that it's always worth it. The great thing now with executing and microservices is that it's cheap to take it for a test drive, at least to try to work out whether you can leverage some of the benefits. Don't underestimate the cost organizationally and culturally?
Reisz: Yan repeated it, but the part that you're solving an organizational problem not a technology problem, I think still resonates with me. Yan, what are your parting thoughts here?
Cui: Always aim for desirable business outcome, not the technology itself. I don't want to say technology is not important. At the same time, it's not why we're doing anything we're doing. It is about creating business value. You need to start from that, and then work backwards to decide what's the best thing for you to do to create the most business value?
Reisz: Absolutely, it makes complete sense.
See more presentations with transcripts