0

What are Entity Components in Distributed Systems?

 11 months ago
source link: https://blog.bitsrc.io/what-are-entity-components-f81d57719f03
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

What are Entity Components in Distributed Systems?

Understanding entity components and how they play a part in building highly scalable distributed systems.

1*Ap5iveOmY8aeUm52pGfkiQ.png

When designing and developing a distributed system, there is always the risk of “re-inventing the wheel” sort of speak. In other words, independent teams working on different parts of the system will see the need for a library or will find code that should be extracted away into a shareable component, and they will not validate it with the rest of the teams.

Which means that halfway through the project’s lifespan, you’ll start seeing duplicated code being extracted into duplicated libraries that have slightly different implementations. Where every team will ignore the other’s contribution and will only use their own.

Lucky for you and your colleagues, there is a pattern + tool combination that can solve all these issues: let’s learn about Bit and the Entity Component.

But first, what is Bit?

Bit is the ideal tool to work with this pattern, it includes and expands the concept of Component-Driven-Design, taking it to the next level.

It gives you the tools to think and design your apps around “components”. In the context of Bit, components are the most basic building blocks, you can create them, use them and reuse them in multiple projects. They can be anything from simple UI elements to entire features, user experiences and even common logic — including the backend.

And don’t get me wrong, we’re not just talking about the classic React component here, anything can be a component for Bit, as long as you can encapsulate it in a way that can be reused later on, Bit can turn it into a component.

On top of that, Bit also provides you with the tools you’ll need to solve other aspects of the architecture you’ll need for a composable frontend. More specifically:

  • Collaboration
  • Component Versioning
  • Code Versioning

And it should come as no surprise, that we’ll be touching all of those points during this article!

Also, check out this guide if you want more details about how Bit and composable frontends work together.

What is an entity component?

An entity component, in the context of Bit, can be considered as any object of interest in the application, like a User, Post, Comment, etc, that is encapsulated into a component. The entity component can fetch data about this entity, hold internal business logic and even do some error handling. And on top of that, because it’s using TypeScript, it also shares the “shape” of the entity itself.

Now, consider the fact that we’re talking about data-fetching logic and business logic being encapsulated, this directly translates to making the component a reusable piece that you can use whenever you need to fetch and display data about an entity, in whatever project you want.

It reduces boilerplate code and keeps the concerns separated through encapsulation and reusability. On top of everything said so far, thanks to Bit’s “powers”, these components can be shared and reused across different projects, improving the development efficiency (remember the part about re-inventing the wheel over and over again?).

So, in summary, an entity component is an abstraction that holds relevant logic, performs entity-specific operations (like data fetching, validation, etc) and eventually handles errors, ultimately for the display of that data to users.

What problems do entity components solve?

Entity components play a crucial role in distributed systems. They provide key solutions to address complexity, maintainability, and reuse of code, among other benefits.

  1. Code Reusability and Consistency: Entity components, once defined and tested, can be reused across multiple parts of the application or even among different applications. The immediate benefits of this are a significant speed up in development time, reduction of code redundancy and support of consistency across the system. The teams we talked about re-inventing the wheel because they didn’t know any better, now have the tools to, instead, properly re-use that code. Bit’s platform specifically excels at this (I’m talking about Bit.cloud here), providing a space where these components can be shared, versioned, and collaborated on easily.
  2. Encapsulation: Entity components encapsulate functionality and data in a single unit, which simplifies reasoning about the code and reduces coupling between different parts of the system. This principle makes it easier for debugging and maintenance.
  3. Scalability: With distributed systems, managing data fetches and state at scale can be a challenge. Entity components can abstract away the complexities of underlying data fetching mechanisms, making it easier to scale up without affecting the main application’s performance or increasing its code complexity.
  4. Flexibility: Since entity components are segregated and independent, when a use case changes or a new one arises, developers can efficiently make changes to a single component or add new components without impacting the overall system.
  5. Efficiency in Data Fetching: An entity component can encapsulate the logic around fetching data (like conducting REST API calls or GraphQL queries), even handling errors. This means you can manage the end-to-end process within the entity component, providing an efficient way to handle data in distributed systems.

In other words, “entity components” help create a more structured, maintainable and scalable architecture when designing distributed systems.

On top of that, using platforms like Bit.cloud enhances these benefits by promoting component sharing and collaboration.

Creating an entity component

Enough with the theory already, how can you create your own entity components using Bit? Let’s assume we have a brand new project, and we want to create a User entity component, we’ll use it to get user information from an external source, and we’ll display that info once it’s available.

As a bonus point, we’ll also share this component so you and others can reuse it!

Keep in mind, that I’m using Bit 1.1.0, so if you don’t have this version, you might want to upgrade your version to the latest by running npx @teambit/bvm upgrade .

Step 01: Defining the components

For this, we’re going to create 3 components:

  1. The actual entity component, which will hold the structure of the data we want to display.
  2. A hook associated with our entity component, that will act as the data fetcher.
  3. A simple displaying component to use the hook and showcase how to use the data later.

Step 02: Creating the workspace

As with any new project with Bit, the first thing you’ll do, is you’ll create the workspace.

For this example, I’m going to use the following line, which also adds several sample components (just ignore those for now):

bit new data-fetching users --aspect teambit.community/starters/data-fetching

This creates a workspace called users (a folder) with everything we need.

From now on, let’s go into the new users folder and run every command from inside it.

Step 03: Setup your scope

This is a really important step if you want to share the components you’re building (which we do). If you skip this step now, fixing things will be really hard.

So let’s go to Bit.cloud and create a scope (if you haven’t yet). I’m going to create one called my-sample-scope inside a fake organization I have.

1*wQ5g4dwdLshXUDVQ72EUIw.png

Now, I’ll edit the workspace.jsonc file at the root of my workspace, updating the value for defaultScope to be: deletemangroup.users-sample

Step 04: Creating the entity component

To create the entity, since it’ll be a class and a type definition, we’ll use a node component, like this:

bit create node entities/users

And then, we’ll make sure to edit the users.ts file to look like this:

export type UserType = {
name: String;
email: String;
phone: String
}


export class User {
constructor (readonly name: UserType['name'], readonly email: UserType['email'], readonly phone: UserType['phone'] ){}

static fromJSON(json) {
if(!json) return null;
return new User(json.name, json.email, json.phone)
}
}

I’m essentially defining a simple object with 3 properties, nothing too fancy. Here you can add as many methods as you need, as long as they’re not about presentation, but rather logic (like calculating the age of the user, or the geo coordinates based on their address).

We’ll also have to edit the index.ts file to change the name of the exported entity. Make sure it reads:

export { User } from './users';

Step 05: Creating the data fetching hook

To fetch the data, I’m going to use RandomAPI, you can use whatever you like.

Our hook is going to be called useUser so I’ll create it with this command:

bit create react-hook ui/hooks/use-user

Now, inside the ui/hooks folder, I have my new use-user folder with all the files I need. I’ll just update the use-user.ts file to look like this:

import { useFetcher } from '@deletemangroup/my-sample-scope.ui.hooks.use-fetcher'
import { User } from '@deletemangroup/my-sample-scope.entities.users';


// Define the entity fetcher as a module or import from a library
async function fetchUser(userId) {
const response = await fetch(`https://randomapi.com/api/3e5978ef64e48d828eb5bed5fcc40dd2`);
const user = await response.json();

return user.results[0];
}

export function useUser({ id }) {
const [loading, error, user] = useFetcher(fetchUser, id);

return [loading, error, User.fromJSON(user)]

}

Note that this is also using another utility hook called useFetcher that abstracts the fetch logic to provide a loading state.

The relevant section of this code, is the use of the actual Entity Component. We’re no longer dealing with the value from the API, but rather, now we’re returning the actual User entity as part of the return values of the hook.

Step 06: Creating the visualization component

Now let’s create a simple React component to show how we use the data:

bit create react ui/user-display

This will create, inside the ui/user-display folder, all the files we need. Just update the one called user-display.tsx to look like this:

import type { ReactNode } from 'react';
import { useUser } from '@deletemangroup/my-sample-scope.ui.hooks.use-user';
import React, { useTransition } from 'react'

export type UserDisplayProps = {
children?: ReactNode;
};

function Loader() {
return (
<p>Loading...</p>
)
}

export function UserDisplay({ children }: UserDisplayProps) {
const [loading, error, user] = useUser({id: 1})
if (loading) {
return <Loader />;
}
if (error) {
return <p>Error: {error.message}</p>;
}

return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
<p>{user.phone}</p>
</div>
);

}

Notice a couple of things:

  1. I’m using the loading value to add a simple transition while the data is being fetched.
  2. I’m importing my hook through an absolute path @deletemangroup/my-sample-scope.ui.hooks.use-user . This is thanks to the fact that Bit is creating symlinks for my new components inside node_modules , this way I can create independent components that can later be shared, and their dependencies are simple NPM packages. I’m essentially treating what is now a mono-repo as a set of individual repos. This is one of my favorite features from Bit.
  3. Finally, the user object here is the actual User entity from before. So here you’d have access to any instance method you might’ve defined. Remember that, because that’s where this pattern shines.

Step 07: Sharing our components

The first thing to do, in case you haven’t yet, is to install any missing dependency with:

bit install --add-missing-deps

Now, we have to “tag” our components, which essentially means assigning a version number to them through semantic versioning. We’ll do that with:

bit tag entities/users ui/hooks/use-user ui/user-display

That command should show something like this once it finishes:

new components
(first version for components)
> entities/[email protected]
> ui/hooks/[email protected]
> ui/[email protected]

Now, all you have to do, is “export” them, which translates to publishing the components into the scope we created. Scopes, in case you don’t know, are logical groupings you can create to organize your components.

To export our work, all we have to do is:

bit export entities/users ui/hooks/use-user ui/user-display

And as a result, you’ll have the components available online, like me here:

1*u7B_wR5kX051hfTOMtccjA.png

And that’s it! You’ve successfully created and shared an entity component using Bit!

How can you update an entity component with Bit?

Let’s now take our example one step further and understand what happens with our entity component and the other components that depend on it, once it gets updated by us.

After all, if we’re working as part of a team, and we push changes, others should be aware of that, right?

That’s where Bit’s Ripple CI comes into play!

Remember: every time we push a component’s new version or updated code (by simply doing bit snap which doesn’t affect the component’s version) to Bit.cloud, it gets built, so it goes through a CI process. Thanks to Ripple, Bit can keep track of every component that depends on our entity component within the same workspace.

Let’s make a change on our entity component and see what happens.

To test what would happen, I’ve updated the original entity component with an email validation method:

export type UserType = {
name: String;
email: String;
phone: String
}

export class User {
constructor (readonly name: UserType['name'], readonly email: UserType['email'], readonly phone: UserType['phone'] ){}

hasValidEmail(): boolean {
const emailPattern = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/;
return emailPattern.test(this.email.toString());
}

static fromJSON(json) {
if(!json) return null;
return new User(json.name, json.email, json.phone)
}
}

Now if I run bit status on my local workspace, I get this:

modified components
(use "bit snap/tag" to lock a version with all your changes)
(use "bit diff" to compare changes)

> deletemangroup.my-sample-scope/entities/users ... ok

It’s telling me that the component got modified, but it’s OK (meaning, ready to go live). So let’s update its version and push it up.

bit tag entities/users && bit export entities/users

That command will bump the component’s version and it’ll push it up.

We immediately, see this in our terminal:

changed components
(components that got a version bump)
> deletemangroup.my-sample-scope/entities/[email protected]
auto-tagged dependents:
deletemangroup.my-sample-scope/ui/hooks/[email protected]
deletemangroup.my-sample-scope/ui/[email protected]

Which means that Bit understands there are dependent components that should also be auto-tagged, to make sure your changes don’t break the existing version.

So if we look at the RippleCI section of our scope, we see this:

1*buywW95QpuTb4ThloacUOw.png

Bit’s automatically re-building the entire dependency chain for us! And once it’s done, the new version of every component will be readily available for our team to update!

Not only that, but if you were to use bit snap instead of bit tag , the changes would cause the same effect, only that instead of generating a new version, the current one would be updated, FOR ALL COMPONENTS within the chain!

Consider that every component, when properly developed has:

  • A set of tests
  • A documentation
  • Live samples that get built with the component

So for every update you make, your entire dependency tree gets “super checked”. Now imagine you have a very complex dependency tree where your entity component is used by many hooks, and those hooks are re-used across multiple components inside the project, thanks to RippleCI, the entire chain gets validated when you make a push, not only your single change.

Concluding Thoughts

Remember, entity components are perfect for storing that business logic (whatever that means for your use case) outside of any representational-related component (i.e if you’re using React, outside any visual component or even Hook).

Because of that, they can be later re-used in other projects or even within the same project, but by other components. There should be nothing inside them tying them to a single component.

Finally, thanks to the fact that we’re using Bit to create and manage them, we also gain other super-powers like:

  • A simple way to share and collaborate on their code with our team and others.
  • A fantastic validation process through Bit’s RippleCI.

Keep that in mind next time you’re planning your distributed architecture, the combo of entity components + Bit, can save you a lot of time!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK