 Hi everyone, I'm Nadia, I am from Atlassian and today I'm here to share with you a story of Jira front-end architecture, or to be precise, to be to share a story of mistakes, revelations and a little bit about human nature. So our very favorite product, Jira, what exactly we're doing there today? So currently we are transforming this very well known and very practical although slightly old and dusty by modern standards design into this piece of beauty. And the biggest challenge in this adventure is that Jira is really, really old. Jira was born in the year 2002 and if we think about it right now it's 2019. It's 17 years. We now have a whole generation of young developers who never lived in a world without Jira. That is slightly crazy for me. So when this idea of big UX update came along, people saw this as an opportunity to get away from all this legacy because as you can imagine 17 years is just lots of legacy everywhere. So they started a new front-end repository and they decided to move only to the modern tech stack. So stuff like Webpack, Babel, React, you name it, we use all of it, we are on the bleeding edge. And this very new front-end repository was so successful and it grew up so, so fast. Then in less than three years we grew to those pretty numbers. Right now we have more than 1.5 million lines of code there, more than 300 developers contributing to it regularly and more than 100,000 commit messages. As you can imagine, maintaining health, scalability and to be honest sanity of repository at that scale is a massive challenge. While trying to solve this challenge we experienced not only technical difficulties of course but also two very fundamental shifts in how we think about our code and architecture of our code. And this is exactly the story I want to share with you today. But first let's step aside a bit from Jira and from Development for a second and talk a bit about human nature. There is this well-known dilemma that is called tragedy of commons. And the dilemma states that if people operate independently in a situation of shared limited resource, if they don't think about common good they will deplete this shared limited resource to the point of extinction. And the very, very basic and very, very old example of this problem is this. We have a field, we have farmers living around this field and then those farmers have cows that feed on this field and everything is okay until one of the farmers they want to make some more money and then they will put more cows on the field. Then the neighbor will do exactly the same thing, the neighbor will do exactly the same thing and if they continue to behave independently like that and don't think about state of this field what will happen is those cows will eat all the grass there and the end result will be inevitable. Unfortunately. So by this time you're probably thinking why exactly am I telling you all those cows story because they're kind of cute but they have nothing to do with software. And if we think about it we can extend this metaphor to our everyday life to almost everything including our development lives. So if I as a developer working in the shared repository contribute something there I am fulfilling my purpose as a developer but cost of my decisions whether I introduce yet another tech debt, another dependency or even slowdown builds is shared between absolutely everyone. So we can think of our repositories, their health and their maintainability and architecture as shared and very, very limited resource. And I'm sure most of you worked in projects that are so fragile and so full of bugs and were navigating through this legacy code looks a bit like this. This is a very, very good sign of shared limited resource problem not solved properly on this project. And of course in JIRA with our huge numbers we experienced all shades of this problem and we tried to solve it from very, very different angles. Starting from the very beginning how it all started. Well if you talk with economists if we return to our farmers analogy they will tell you that if we have small community like for example our farmers leave somewhere in the forest isolated from their neighbors they will probably be fine, they probably won't even have this problem, they will be able to self-regulate themselves. And in software we also see this all the time. Small, stable, motivated teams working on small projects they usually find. They usually able to talk with each other to resolve the differences and everything is okay. And that is exactly how we started in JIRA. At the very, very beginning we only had two teams, they were working on two applications. Those applications were completely isolated, no communications with each other applications, no teams, people were talking all the time of course. And it was really successful, it worked really, really well. And after a year what happened? Our farmers they live in this happy small community and of course they are bragging to all their neighbors to all their friends about how awesome life in this community, how all these new features are so cool to work with. And their neighbors and friends they hear of course and they start coming and then more and more and more. They settled around this field, they built their own houses around this field, they even brought their own animals to this happy neighborhood. So in terms of software and JIRA what exactly happened is we found ourselves in this situation. We had something like 50 applications with 50 different architectures and sometimes even different text tags, which is not cool at all. In this situation it's very, very hard to reason about your repository. You get used to ways your team does things, it's curcue ways and then you need to fix a bug in another team's domain and welcome to another marvelous universe. Everything is so different, you don't even know where to start, everything is probably upside down for you. Sharing code or sharing knowledge in this situation is also quite hard. Nothing is compatible with each other, you don't know what's going on outside. So people's teams stuck with their own tiny silos and then they stuck in this loop on reinventing the wheel over and over and over again. So this is the point where we experienced our very first revolution. We needed to deal with this situation somehow and basically what we wanted to is we wanted to transform this pretty happy farmer's situation into something like this. We wanted to have more consistency, more sanity and at least those farmers to live in similar cottages and don't reinvent their own design for all those cottages all the time. And if we ask economists, they will tell us. So when small communities outgrow themselves or outgrow this self-managing situation, this is the point where people invent in governments. We need rules and regulations, we need some sort of law in order to help us to resolve conflicts and to help us navigate through everything. And this is exactly what we tried to do in JIRA. We introduced a program that we call by reasons that no one understands Tangerine. And Tangerine is a program that was designed to bring a consistency and standards into this slightly chaotic world. At its core it was standardization. Standardization on patterns, practices and technologies and to apply all those patterns and practices to every single piece of code in JIRA front-end. So we invented lots of lots of things under this program. So we designed processes where you can introduce new patterns that you want to standardize in the repository. Like for example, we had an idea of Tangerine slices where your pattern that you want to standardize on goes through different stages like from an idea then through proposal where it's discussed with different teams and different people. Then to candidate where it's almost formalized and ready to be implemented and to final when it's actually implemented through the entire repository. Also we had a committee that was supposed to help guide and help teams with this whole process and basically they were supposed to be in control of everything. So we standardized on lots of lots of different patterns and practices. From tiny, tiny things like how exactly you should type your arrays to some big patterns like architecture of every single application in JIRA front-end. So I will share with you couple of those architectural designs today. So first of all, we introduced an idea of an app. An app is a cornerstone of development in JIRA front-end. One JIRA developers talk with each other, app, app, app, you can hear it all the time. And if we look, an app is nothing more than just a feature. Usually self-contained, isolated and that can behave independently. So for example, on a very typical JIRA page, an app will be a navigation that deals with navigation concerns is an app. Or a board also will be an app. Or if you click on an issue, you will see a big issue view, it will also be an app. From technical perspective, those apps are nothing more than React components since we're using React. They communicate with outside world through API of props and callbacks. They sometimes have views, state side effects. The important thing here is every app is encapsulated and from the consumer perspective, everything that's inside is a black box. They don't know anything, they know only external public API. After introducing ideas of those apps, we dove deeper and we standardised on structure for every single app in JIRA front-end as well. We standardised on very, very strict separation of concerns for all of those. So every single app would have layers of concerns. It would have view layer that is managed by React and has React concerns like styles or components. Then it would have state layer if it needs states and the state will be managed by Redux. And also it will probably sometimes have side effects layer that will be managed by another library. And deal with external requests and data transformations and fun stuff like that. We of course didn't stop there. We were on the path of standardising everything. So after standardising on those layers, we dove even deeper and we enforced very, very strict structure for every single layer here. So for example, for view layer, we were saying that every single view should be structured hierarchically. Every single view should be separated into presentational and connected view. They will have naming conventions. They will be connected with connected views to your external Redux store. Every tiny detail was there. And the most important thing here in this programme was tooling. But at that point, we have something like 100 developers contributing into a repository. And in this situation, even if you standardise on the best practice ever, if it's not enforced somehow, it probably doesn't exist because people adjust people. They make mistakes. They forget. Sometimes they didn't know something. If you can enforce something with tooling, this is the only way to go when you're dealing with scale. And we're trying to enforce absolutely everything. We take a very, very big advantage of all forms of static analysis tools, especially YesLint. We love YesLint. We use all the standards available there, like Airbnb or Prettier, you name it. Also, we are taking a very big advantage of YesLint's plug-able nature and we implemented lots of custom YesLint rules that help us enforce all those conventions, from tiny naming conventions to this whole structure as well. Like, for example, in this scenario, we can say we want all of our views to be named exactly as the folder. This view lives in an import section that lives in a folder section and we enforce this with YesLint. And most importantly, most of our rules are auto-fixable. So, because of that, since almost everything is auto-fixable, developing in GeoFront and Repository is a very, very interesting experience. So you type something, then you press Save and your code is just changing under your fingertips. It's a very, very weird experience at first, but when you get used to it, you completely screw it. Because you will never be able to develop in any Repository ever again. This is just so convenient for you. On top of YesLint, we also have another tool that we call Strictor. It was developed in Atlassian, but it's open source. So it's very, very cool. Strictor is something like YesLint to your file. Strictor has access to files, to folders, to content of those files, and to dependency of those files. And with Strictor, we can enforce structure of our entire app, or we can find circle dependencies, or we can enforce naming conventions on folder level. Like, for example, we can say that we have layers in our apps, and those layers name exactly that. And if you want to introduce something random here, we will just fail your build. Strictor won't allow you to do this. So after a year of this program, what happened? On the plus side, we now had 200 apps. We will continue to grow very, very rapidly. And the good thing was that every single app was structured in exactly the same way. For example, two completely different apps. They are written by different teams, but if we look closely, we can see that they both have ops layer, state layer, and even a view layer. And by this view layer, you can roughly understand what's going on, reading just folder names without even starting this app. So this is kind of cool. If we look at this farmer's metaphor, we transition from this into this. All of our farmers, they still live around this field. They live in the same cottages, and they're all growing tangerine on this field. Of course, does anyone believe that? What in reality we found ourselves in a situation something like this? Or probably even this. I call it tangerine tyranny. We implemented this tyranny. We were telling our poor farmers where exactly on the square field they should live, where they should grow their tangerines, where the cows should be named. Very, very strict. We were way too strict, and our processes were just way too rigid, and especially not really scalable with our growth, because at this point we had sent like 200 developers now. On top of that, our tech stack failed us. React evolved. React introduced us very new interesting features like suspense, or context API, or very fancy streaming, stuff like that. We barely could take advantage of those, because we were stuck in this very, very layered nature of our apps, and half of our apps were managed outside of React by Redux and other libraries. Also Redux. Redux is a very good tool for structured state management when it's just one tool and when it just owns all the state for your entire application. But when we were separating our apps and isolating them, and every single app would have its own Redux store, it became complicated really, really quickly, because you cannot compose your Redux store, you cannot share information between those, and you cannot even use context API easily, because you need to sync it with different Redux stores. So it's very, very complicated. Also extracting sub-apps from those apps turned out to be also really hard, because on one hand we have those layers of concerns, we have view and state, and they separate it, but not exactly, because if you ever worked with Redux, you know that Redux sits on top of your view, and it spreads its tentacles everywhere, and it connects to every single view everywhere, and it's just like a big giant sea creature with tentacles that you cannot get away from. If you want to extract small concern from this app, you need to cherry pick piece of your view, and then somehow cherry pick piece of your state and all those selectors and actions, and in reality it's so, so hard that people rarely did it, and if you screw your architecture of your app from the beginning, you're stuck with it, you probably won't even think of refactoring, it will continue to go on like this forever. So this was also a problem, and this is the time where we experienced our second revolution. We needed to deal all of this, and basically what we wanted to is to get back to the initial idea. We wanted to have laws and order, we wanted to have some structure and some guidelines and patterns, but we wanted to get rid of this tyranny, and we wanted to solve the Redux problem. So we rebooted. We rebooted absolutely everything starting with the name. Now we call it Tangerine Next, very original, and the name we started from the text tag and patterns. So we completely rethought of how we're thinking of our apps and especially how we're thinking about separation of concerns in our apps. So for example, before Tangerine Next and Tangerine Classic, how we call it now, all those layers of concerns, view, state, and side effect. And if you look a bit back into the past of front-end development, in very, very old days, like five years ago, when front-end development wasn't cool, we used to separate our concerns like this. We used to separate our concerns as JavaScript layer, CSS layer, and HTML layer. We went in different patterns and different frameworks to manage all those layers of concerns. And then React, and CSS, and JS came along, and now everything just went south. And now we're merging everything in just one file and managing all those concerns in just one file. And what in reality happened is nothing changed, actually. We just looked at exactly the same concerns from slightly different angle. And instead of seeing HTML, CSS, and JavaScript layer, we now see that there is a form that may or may not contain HTML, and CSS, and JavaScript. Or there is a button which is also its own concern or navigation. All of those are exactly the same functionality. Only we are separating our concerns now by context, not by technology. And this is exactly what we did, and this is exactly what we did in Jira. Instead of separation of our concerns by layers of technology, like view, state, and side effects, we started to think of our concerns and separating our concerns by context. We started to see that we have navigation concern that have some views, some state, and side effect. Or maybe we have a draw concern that probably doesn't have any side effects, but we have extensive view and state, or probably even starting functionality where you can add something to your favorites, and then it will just appear everywhere. This concern probably will have some side effect and state management, but probably won't even render anything. And this is also totally valid concern from user perspective. So after rethinking all of this, we continue our reboot. We set a path to be more closer to the roots. We are now managing our state by React only, so we're trying to get away from Redux for state management. We use components as basically unified interface for everything, including fetching data and state management and all those stuff. And also we completely rethought what is state. Because when you live in a world of Redux, you tend to not think about it. React Redux equals state, and then state equals Redux, and this is just the way it is. But when we get away from Redux for a second, we can see that we have completely different patterns in our state, and those patterns can be implemented by different methods that are more suited to those patterns. Like, for example, we extracted at least four different types of state. Local state, remote, async, and shared state. So local state is the most simplest one of all. Your dropdown, whether it's open or closed, is what we call local state. Or in case of a form, when you type something in a field, you want to show more selectors. When you type everything when you fill this form, you want to unblock your submit button. This is what we call local state of this form. Remote state. When you have some outside data, like database, and you want to sync this data to your UI, this is what we call a remote state. This state is not even owned by your app. You're basically copy pasting it in your local UI. And this is completely framework agnostic. You can implement it with RasterQuest, with GraphQL. It doesn't really matter. It's just syncing with remote data. The process of syncing it to metadata about your remote state is what we call async state. Whether it's loading or whether you throw an error, is a sync state of your remote state, and they usually go together. And then shared state. Shared state is the most interesting one of all. Imagine we have a situation of, we have navigation on the left, and then navigation can be expanded or collapsed. And then based on whether it's expanded or collapsed, we want to render different layouts. Different layouts of this form. And then somewhere outside of both of those, we have a button by click of which button, you can expand or collapse navigation. So this idea of navigation state as its own entity can be extracted, and this entity can be extracted into a component, is what we call a shared state. And of course, since we are using React, and components composition is the major, is the basic React button, all of those types of state can be composed with each other. And this is a really powerful concept that allows you to implement really, really complicated interfaces by just simply composing different types of components. So to manage all of those types of states, since we wanted to have structure and we wanted to preserve all the good things that we had in Tantran Classic, we introduced three different types of components. We call them UI services and controllers. And UI controller is anything that can be rendered on your screen, like for example, a button with props and callbacks is just a basic UI component. Services are components that manage your remote state, as I mentioned, so they will accept some props and they will provide you API through render props to listen for data and to metastate of this data, whether it's still loading or whether it's here. And then controllers are components that implement your shared state. And then again, they are just components. They accept some props, and then they will provide you API to listen whether navigation, for example, from our shared state example, is open or not, and allows you to operate on this state to expand or collapse this navigation. So after rethinking this whole patterns and everything, we also took a closer look at our processes. So we rebooted them, of course, as well. We got rid of Tantran committee first, now we have Tantran champions. Those champions are representative from every single team in Jira, and they are helping those teams with all the new patterns, and then they're basically beacons of light of Tantran in their teams. Also, we rethought our process. Now it's much more fluid, much more flexible and much more iterative, so we can propose small things and it can be accepted really, really quickly. And also we even introduced a bit of gamification into all of this to encourage our developers to follow all of our guidelines. Like, for example, we have a dashboard on which you can see status of your apps in terms of Tantran compliance, whether they are completely Tantran, Classic or Next or not at all, you can see scoring for your apps, and also if you really want to, you can even take part of the best package ever competition. And you will be surprised how many people actually very, very excited about this competition. So what about now? What is our current situation? Well, instead of this tyranny, we transitioned to, well, at least I hope I believe, we transitioned to something like this. We're much more flexible, but we still have rules, we still have a law and order, we're still telling our farmers that they should grow specific animals on their field, but probably on the mammals, probably not really with crabs there, and we're telling them, well, don't poop on your neighbor's garden. Also, we even have, we still have a law and order, we still have some sort of government, and also we have local superheros from local communities that help establish this law and order. So in terms of our tech stack and in terms of our structure, from this tiny monster of a state, we transitioned into this. Now every single app is just a components composition. Those components, they are structured hierarchically, they can encapsulate different concerns within those, some of them can be just a view, some of them can be just a state, some of them can be just a side effect, some of them can compose different, lots of them, but they all just react components, and if you need to extract a sub-app from this particular app, it can be in theory just a drag and drop, and here you go, your new app. So this is a really, really powerful concept for us. And takeaways from all of this. When you're dealing with scale and especially when you're dealing with scale and rapid growth, rules and regulations is something that you would want to take really, really seriously from the very beginning. You cannot rely on people just talking with each other when it's more than one, maybe two teams, you need to have some sort of government. Also with React world state management is a very, very interesting and very, very complicated problem, and you have to get it right from the very beginning. So at least you want to make sure that you don't buck yourself into a corner when you're dealing with state management and React. And finally, people and processes don't underestimate those problems and don't think that every single problem is a technical problem. People are very, very important and processes around people also very, very important. You have to get those right as well, so you don't find yourselves in the situation of either complete chaos or tyranny like us. You want to have some sort of balance. And that's kind of it. Any questions? So I just want to know like there are a few things which like the benefits provided by the Redux. So how you manage those benefits like easy debugging kind of things. So the question is how we manage debugging and other nice stuff without Redux, right? Yes. So it's a very good question because when you're using React context API, you don't have much flexibility that Redux provides you. So for this we have our own state management library that is just an abstraction on top of React context and it provides you very interesting functionality like you can select pieces of your big store and also you can emit actions. So it merges best of two worlds of Redux and React context and composition patterns of React. Also this library can be hooked up to Redux DevTools. So you can see all those actions in Redux DevTools and it's also open source. It's called React so if you want to Google and if you want to use it, it's there. And one more thing like maybe it's not recommended but we can use the multiple stores for Redux as well which may separate out the concerns of the different sub-apps into a bigger app. So is there anything which you think about those like having multiple stores for every sub-apps so that whenever you separate out those apps their states are with those apps. So there are different Redux stores for different different apps. It is not recommended but I think it's allowed in the rest world. So it's allowed to have different Redux stores. When you compose different apps with different Redux stores they just completely isolated from each other and they need to share this data only through props and callbacks and probably render props. So this is when you want to share data between those two you want to extract this into some sort of concern that deals with this data. Like in case of navigation controller that deals with navigation being open or closed and then those two apps can just compose this navigation component in those two apps if this makes sense and if this answers your question. I have a question here like how is the build system implemented like you have like in number of apps like how is the build system like do you guys build it separately or you combine the apps and build it together and how does the communication happen between one app to another app like who will load the other app. Sorry I didn't get the question. How is the build system implemented? Build system. So all of those apps we're building everything with webpack so all of those apps are within SPA so it's just unfortunately for now very big monolithic build for all of them. So we have SPA shell component that composes different pages and within those pages is a composition of different components and those components are apps so from React perspective those apps are just components and this is just composition and with webpack we are building so SPA shell is just a component and then those pages are async imports and then webpack just goes through all of them and builds a huge number of bundles. Thanks. Hi Nadia. Actually I was an end user before but I understood about the friend architecture and all these things about Jira now so I would like to ask you a question like in 2010 you had a cross-site security vulnerability in your system and you got attacked and you have to compromise some of your servers, Apache servers and actually you told people to restore or change their passwords so how did you handle these situations as a tech lead what will be the first measures you will take if something happens to our servers like it's like fixing a car while driving so you might have a lot of users will be using the system how will you handle these situations in a short if you can explain then it's good. That sounds like very NDA question to be honest. Yeah I'm not really sure how to answer that I would need to think maybe we can catch up after. Yeah thank you. Hi Nadia. With respect to the design I have one question maybe due to adoption I'm pretty much more comfortable with the older version of the Jira rather than the new version of the Jira can you point out some of the key features which you have included with respect to the new design and as a user what are the advantages I will be that in with respect to the new design I'm sorry I didn't get the question. Okay I'll make it short. What are the advantages I'll be that in with respect to the new design advantages advantages of the new design of Jira first of all it's simplified we are trying to simplify experience because Jira is very very complicated and also it follows our new design system and our new guidelines so we are building as we heard from the previous talk we're building something similar we're building design system and all of those are following our design guidelines as well and of course we are from technical perspective we are separating ourselves from legacy so it's much more developer friendly environment to work on. Hello I want to know I want to know what we should put in the local state and what we should put in the remote state how do you differentiate between what should be in the local state and what should be in the remote state because let's say if you have a form you are getting data from the remote server but you are also editing data in the same form itself so that is local too but you are also interacting with the rest services so how to differentiate between these two states so we are trying to make everything as simple as possible so at the very very beginning when you just start to develop your component you probably can even merge your concepts of local and remote state in just one same component that will be just normal fetch and then you will just use your local state to store this data but when you need to reuse exactly the same fetch in some different components or when it becomes slightly more complicated to manage within just one component at this point you would want to extract fetching of your data into its own component or hook and then then in this case you want to decompose your app and then within services we are telling people that services should be just leaf components they should encapsulate only fetching functionality and provides us only data and then is loaded or is erring so for consistency and so when you dealing with services within all of our millions lines of code you always know what exactly is going on so even if you not develop the service you know that it will provide you data and it will provide you is loading or is error and then when you need to deal with this data somehow then you will store it in probably your local state or maybe in some sort of controller and you will just render this in your UI.