This video walks through the process of creating the first model, which provides the system information fetched from the backend API on UI.
Models define a contract between the backend dispenser and frontend UI, and provide an integration point between them. Once the model is created, we register that with the framework, which instantiates the model on startup and the logs messages.
Okay, let’s jump in and create our first model.
The first one we’re going to create is going to show some basic system information to the user.
For the first exercise, though, we’re really just going to want to be able to create the model manually so we can see what’s involved, and also show how the models can be used as that contract between the backend dispenser and the UI, and how it can provide a nice integration point between them.
So to begin with, let’s talk about the data that we’re going to fetch.
So if we go into our Postman plugin, we can see down the side I have an endpoint here, which is going to one of the KOS APIs, looking at some descriptors that have been configured on our system application, which is currently running, as we had shown before.
When I hit send, you can see the data that’s coming back.
So this is common amongst all of the endpoints.
There is going to be a status in the payload, there’s going to be a version, but most importantly, there’s going to be a data structure that we want to be able to pull out and make use of in this particular model.
For now, we want to have a model that’s going to provide to us the app class and app ID.
Eventually, we want to be able to show it on the screen, but for now, we want to just make sure that we can create a model that represents this system information in a way that can be used by the UI team.
So to begin with, let’s go into our code, and we’ll start by creating a model in our model project here.
So here we have the lib folder, and we’ll create a new folder for system info, let’s call it.
Now to start with, we’re going to create an interface that represents the data that we want to capture.
So this will be, we’ll create a directory called types, which will hold all of our interface and TypeScript data.
This will just be a declaration file, and in here, we’ll export an interface, and we’ll call it the system info model, and it’s going to have a few things, like an ID, but then we’d said it’s going to have an application name, an app class, and an app ID.
We’ll just take that straight from payload here.
It’s going to be a string, and the app ID is also a string.
So this is really defining the public interface for this particular model, and it’s going to be used by the UI when it needs to have access to this information.
All the other information about how the model works and everything is really encapsulated in the interface that all models need to extend, which is the KOS data model that I’ll import from our core UI SDK.
Now there is another type that I’d like to create, which is called the options.
Now this one, I tend to leave empty to begin with, and what these options represent are additional data that might get passed into the model to initialize it.
We don’t have anything that we need to initialize it with right now, but I’d like to create it anyway just so there’s a placeholder for it.
That’s really all there is to the interface, and this means that this is something I can hand off to the UI team and they can start building against it if we needed to, but let’s create an implementation that goes along with it as well.
So to do that, we’ll go back into our system info, create a new file, and it’s going to be the system info model TS.
Again, same sort of thing, we’re going to export a class, which is our system info model implementation, and it will implement the type that we created before, the system info model.
Remember, that already included all of the information about the KOS data model.
Now it’s giving us an error message here, a quick fix, and say implement the interface.
There’s going to be a lot of stuff here that we don’t really need.
You can see a lot of it’s optional, actually.
These are all methods that are available to us on the KOS data model interface.
It really allows this thing to communicate as part of the framework, so the model framework might use these things.
A lot of these are lifecycle hooks and such, but we’ll get into those in more detail later on.
But for now, we can probably just get rid of them.
So we put in the data that we need, and I’m just going to create a constructor that represents it.
This constructor for a KOS data model for any model participating in the framework accepts three parameters.
One is the model ID.
The other is the options, which in this case is going to be of the system info options that we talked about.
And there is a third, which is the context.
This is actually created by, injected into the framework automatically for every model that gets instantiated, and this is of KOS creation context.
So our constructor accepts these three things, and we’re going to set, initialize a lot of our data.
So the ID, as I said, is passed in, so we can set that to the model ID.
We have the app class, which for now I’m just going to set to a default string.
And then the app ID, I’ll also just set to a default string for now.
We’ll get into more of that in a little bit.
So we’ve now created it, but sometimes it’s a good idea to be able to log some of the information.
The good news is there is a logger that’s available, and that’s made available to us on the context.
So the framework will actually initialize a logger for us, which is typed to our model here, and we’ll be able to pass that in, you know, it’ll get injected into the class, so we can make use of that.
And then logger is KOS context logger.
So we have that in place, but to really have this thing declared as a KOS model, so it’s implementing all the right features, but the KOS framework makes use of a decorator system.
So I can create a decorator on the class, which is a KOS model.
Again, it will be imported from the core SDK.
I’m going to provide it the model type that it’s extending.
So in this case, I’ll put in the interface, so system info model, as well as the options.
And this is useful so that the framework has the ability to validate the data that’s being passed in.
So system info options, and here I need to put in a type, right?
So the model type is a string that uniquely identifies this shape or this type of model, right?
So let’s give it a simple system info model, and I’m going to pass that into the decorator as a parameter.
Now, this is useful for when we go to instantiate these things, because the framework exposes a set of factories that we can use, and it allows us to use this type, the string, to identify how it’s going to be instantiated and how we want to have it treated within the system.
So the model type is important, and we’ll use that later on, but basically for now we have a constant here, and it’s registered in the decorator to say what type defines this particular model type.
Now that’s pretty much all we need to get started.
I can use this model, it’s not going to do much, but I do want to use the logger here to just say that I’ve instantiated it.
So I can say this logger info, and we’re just going to say we’re creating the model type here.
Not terribly exciting, but it’s enough to get us started.
So the last part that we need in order to really use this model is to register it with an application, right?
So every model that’s going to be used as part of a UI application is registered as part of the app in a registration TypeScript file that exists.
When you scaffold out a project, the registration is already there for you, and some of the models, a lot of the ones that are provided by the core, as well as some of the additional SDK models are already mapped in here, so we don’t really need to worry about it, but we do want to include the registration here.
You’ll notice this pattern here where a registration is being spread into the model registration.
We’ll get into that later on when we start generating our models using the tooling.
It’s really just a convenience.
For now, we’ll start off with the manual way of doing it.
So a registry, a model registry requires a type and the class, the implementation class that you want to use to instantiate that model, as well as a little bit of other metadata.
So we already created the type, you might recall down here, and it’s being exported, but I do want to make sure that it’s being exported out of the model project itself.
So I’ll just say export star from libsysteminfo, and in the libsysteminfo, I’m going to create an index.ts to sort of barrel all this up, so we can expose what we want out of this particular module.
So I’m going to export a couple of our types, our systeminfo model and our systeminfo options.
For now, I’m going to export the model type, but I’m going to give it a unique name so it doesn’t conflict with anything else.
And I’m also going to export the systeminfo implementation.
I don’t normally recommend having to do this.
And when you see how we generate models, a lot of this gets hidden so that you don’t run into cases where you’re potentially using the implementation class where you really should be working against interfaces.
Just makes for a cleaner separation and allows the interface to really represent that contract between the various sides.
So for now, I’ll put it there because I needed it externally, but we’re going to clean that up as we go through.
Let’s go back to our registration.
So one piece that we need is a type, two main pieces that I need to worry about.
There’s the class that’s going to be used and whether or not it’s a singleton.
Now a singleton says how many instances a particular model can exist in the system.
For our particular case here, we don’t want to have multiple instances of the model coming along.
So we’ll just create the one case right here.
We’ll create the one instance, it won’t be duplicated.
And so from a framework perspective, it’s able to keep track of that and ensure that it’s properly cached and only ever has a single instance around.
And this is a common pattern that you see.
So for the purposes of this, rather than stitching into the UI right now, I just want to make sure that the model itself is functioning, and that brings in another part of the registration that’s available.
So I can declare certain models as being preloaded.
All I need to provide here is the type into the registration.
And the framework will be able to pick that up.
And what that says is it guarantees that that model will be loaded and all of its resources and dependencies realized, and it’ll be available for use in the system.
So I don’t need to do anything to load it explicitly.
It will just get handled at the beginning.
And it’s really useful for these singleton models, especially those that I’m not passing any data into.
So it basically makes it available to me.
So if we take a look at our class, I’ll bump this up just to make sure that it’s going to appear.
I’m putting in a logger message, and we want to be able to see if it’s getting created.
So one of the things I can show you in the application project is there’s an env, an environment variable that I can set here that’ll allow me to make sure that it’s going to get logged for me.
So the front-end build process uses Vite.
And what that means is we can pass in environment variables into the build process, and one of them is going to be the log level.
So I want to say I want to log at the debug level and higher.
So we can actually see if that’s been created when I open up the dev tools.
You can actually see the message that’s been in here where it’s created that model.
So what does that mean is that we’ve been able to create a model, we’ve been able to register it with the framework, and as part of the preload, it’s been able to instantiate that model on system startup and the messages being logged to the system.
Thanks.