Check out a free preview of the full State Machines in JavaScript with XState, v2 course

The "Software Modeling" Lesson is part of the full, State Machines in JavaScript with XState, v2 course featured in this preview video. Here's what you'd learn in this lesson:

David discusses what software modeling is, event-driven architecture, how to specify behavior in an application with given, when, and then. Event-first compared to state-first, state machines, and statecharts are also discussed in the segment.


Transcript from the "Software Modeling" Lesson

>> We're gonna be starting with our first lesson which is on software modeling. Instead of jumping right into state machines and statecharts, I really want to get down to the why of state machines and statecharts. And why you would actually want to use these types of things when the way we've been coding really didn't touch on state machines or statecharts for however many years we've been coding.

But why it's important today and should have been important the entire time we've been developers. So the first question is what is software modeling? Software modeling in short is the art of planning ahead, at least in my own words. Really, if you look up what software modeling is, you see that it talks a lot about abstractions.

And that's really what software modeling is about is creating a layer of abstraction that is one degree separated from the code. So the code is the implementation layer and the model is the abstraction layer. Now, there are many ways to model software, one of the most popular ways and useful in my opinion to do so, is by using diagrams.

So, you might have started planning software in the past by just grabbing a whiteboard or maybe a pencil and paper and drawing user flows or flowcharts or diagrams like entity relationship diagrams. And just trying to understand what your code is supposed to do and accomplish at a higher level.

And so this essentially is what software modeling is. It's creating these abstract models that we could code against. However, something that I see way too often in software development is jumping straight into the code. So we'll look at something like a user interface of something we're supposed to implement and one of our first reactions says, okay, let's start coding the user interface.

And then we start adding functionality on top of that user interface by putting a lot of logic and event handlers. And if something needs to be different, then we'll add an if statements. And this is what quickly creates spaghetti code. So this is something that we really wants to avoid just by planning ahead.

Now, one of the problems or I'd like to call it a friction points with diagrams is that diagrams aren't always up to date with the code. We might draw a really nice architecture diagram or a flowchart describing our code. But once we starts to code, and once new requirements come in, it becomes double efforts to both update the diagram and also update the code.

So that's what state machines and statecharts are for. And we're gonna talk about that in a little bit. But it allows you to do both the software modeling upfront and the actual implementation at the same time, and do it in a way that you could change the model and the implementation and they could stay completely in sync.

And so this allows you to add new features, change features, and really understand your software as a whole in one step. Instead of doing it in multiple steps of creating a diagram, then code into the diagram, or even skipping the diagram altogether, which definitely not a good thing.

So state machines and statecharts are part of what's called events-driven-architecture. And so event driven architecture is about the fact that you have events as a primitive, anything that can happen to whatever app or software that you're programming is an event. So something that a user does, whether they click something, or swipe something, or type something in, all of those are events.

And by events I mean things that happen which is the literal definition of an event. So when the user clicks a button, that button clicked events is an event that can be fed into this model that we created and so that model can decide what should happen next based on that events.

Now keep in mind that events aren't just something that originates from the user, but events can also be something that originates from the system or other systems that are interacting with your software itself. For example, if we start fetching a promise, then the user action of clicking a button or pressing answer on a phone to start fetching that promise is an events.

But also the promise resolving or rejecting, those are also events and they don't come from the user. So it's important to keep in mind that events encompass anything that can happen in your application, and not just things from the user. So how do we specify behavior in an application?

This is where things like given-when-then and test-driven developments come into play. And these are really loose specifications for how we talk about how our application is supposed to behave. Now, let's actually talk about the media player for a minute because I want us to start creating a specification on how it's supposed to work.

And you could see that specification if you go into modeling and this readme. We have a loose text description of what is supposed to happen in the app at each step. Now I actually really like given-when-then. And that's because when we represent a specification in this given-when-then structure, we already have all of the building blocks we need to make a really solid model of our application.

So, for example, we say we have here a song when loaded will start playing by default. So, we could say that given, and I will bump this up. Let's just use large over here. Given a song is not loaded yet. When a song is loaded, then the song should start playing.

So now we have everything here that represents just the different building blocks of our application, or at least one small part of our application. So, this given over here is a state, is a precondition, but it could also be represented as a state in our application. So we had this idea of a song not being loaded yet.

And then this one represents an event. So when a song is loaded, so now we know that that loading, or that loaded part is an events that can happen in the application. Then the song should start playing. So now we have a different states. If the states aren't changing, then this then part is going to be exactly the same as the given.

But user specifications usually specify that the state of the application does change. So we have a bunch of these user stories. The user could also play or pause the song and we could translate that to, for example, given the song is playing, when the user presses pause, then the song should be paused.

So now we have our playing states, our paused state, and the events of clicking the pause button to pause the song. So, that's why specifying behavior is so useful and this is why we typically do this using user stories because it gives us all of the building blocks of specifying our application.

>> So is modeling making a list of all possible events that can occur in an app?
>> Yes, exactly. Like I was talking about before, typically when we start coding an app, we start coding with the user interface and just start shoving events in there. So now that we're thinking in an event-first architecture, event-first really describes that there's a bunch of events happening, and our application is going to react to those events.

But it can be really tempting to put all of the logic inside of event handlers for this. And this typically isn't really the best way to go especially as the app scales up in features. For example, you might be putting fetch logic inside of a form submit handler.

Or you might be having like defensive conditions and if statements inside of a click handler that determines what should happen when this button is clicked. For example, you might say if the form is loading and the submit button is clicked again, then we might have an if statement, like if, if is loading, then do nothing, otherwise start fetching the data.

And this makes the behavior implicit. It makes the behavior such that we're describing the application in terms of when this event happens, do this, but also keep in mind these conditions. And so now instead of thinking in terms of like the entire state of the application. Or thinking in terms of okay, let's have an event just do something and if that's not doing the exact thing we want it to, add more if statements in there so that we get to do the behavior we want.

And so that's why I encourage you to take a step back into a state-first approach instead. And so I'm going to demonstrate what we mean by this. And this is also going to get into state machines and statecharts as well. So first, when we look at this, this specification, we have a state-first approach already over here.

We say when the song is loaded, then the song should start playing. But we have this precondition over here too, given a song is not loaded yet. Now, we can make another user story that says, for example, given a song is playing, when the pause button is pressed, then the song should be paused.

All right, so this specification allows us to prevent any unintentional behavior of, for example, if the pause button is pressed when the song is already paused, if you have a pause button visible. Or if the play button is visible while the song is already playing and the user happens to press it again.

Some unintended behavior might happen if the exact same button is pressed more than once and we're attaching logic to the event handler instead of considering a state-first approach. And so that's why this given part of the given-when-then statements are so important. And so like I was saying, this gets into what state machines and statecharts really are.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now