paint-brush
Do We Really Need to Separate the Model from the UI? TLDR: No.by@mvila
682 reads
682 reads

Do We Really Need to Separate the Model from the UI? TLDR: No.

by Manuel VilaNovember 22nd, 2019
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

Typically, domain models and UI views are completely separated. But now that we have functional UI libraries (e.g., React with hooks), wouldn't it be possible to gather everything together, and implement the views as methods of the models they represent? We would separate the view from the model like the following: To render a UserView, we would do: The more verbose approach brings benefits? No matter how much I scratch my head, I don't see any. The code is just more scattered, but logically, it is not.

Companies Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - Do We Really Need to Separate the Model from the UI? TLDR: No.
Manuel Vila HackerNoon profile picture

Typically, domain models and UI views are completely separated. A few years ago, we had a good reason to do so because the views were mostly made of imperative code. But now that we have functional UI libraries (e.g., React with hooks), wouldn't it be possible to gather everything together, and implement the views as methods of the models they represent?

Object-Oriented Approach

For example, let's say we have a 

User
 class defined as follows:

class User {
  constructor({firstName, lastName}) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  getFullName() {
    return `${this.firstName} ${this.lastName}`;
  }
}

Now, let's add a React component into this class:

class User {
  // ...

  View = () => {
    return <div>{this.getFullName()}</div>;
  };
}

And create an instance:

const user = new User({firstName: 'Arthur', lastName: 'Rimbaud'});

Then, to render a 

View
 for this 
user
, we can do:

<user.View />

This is a perfectly valid JavaScript/JSX code, and I don't think there is something wrong with it. Conceptually, the 

View()
 method is no different than the 
getFullName()
 method. They are just methods returning a different kind of view: 
getFullName()
 returns a string and 
View()
 returns a React element.

Functional Approach

In a typical React app, we would not do so though. We would separate the view from the model like the following:

function UserView({user}) {
  return <div>{user.getFullName()}</div>;
}

Then, to render 

UserView
, we would do:

<UserView user={user} />

Does this more verbose approach bring benefits? No matter how much I scratch my head, I don't see any. The code is just more scattered.

Decoupling

It is always good to decouple the pieces of an application as much as possible.

But does the functional approach (React components implemented separately as functions) brings more decoupling than the object-oriented approach (React components implemented as methods of a model)?

It doesn't. Getting the models from a parameter or accessing them through 

this
 makes no difference. In both cases, models and views become tightly coupled.

Separation of Concerns

Some might argue that it is good to separate the model from the view because they are two different concerns. I don't get it. Again, how, in the object-oriented approach, the 

getFullName()
 method is different than the 
View()
 method? Both are returning a representation of the model, so why should we separate them?

It remembers me of the discussion about separating HTML and CSS. Yes, they serve two different purposes. HTML describes the content and CSS describes the presentation. But I don't think there is something wrong about putting them together in a cohesive way.

Sharing One Model with Multiple UIs

Let's imagine we are building an app for several platforms: a web app (with ReactDOM) and an iOS app (with React Native).

In this case, we usually want to share the same model with all platforms, and implement different UIs for each platform. To achieve that, we can implement the model separately and subclass it to implement the different views.

Refactoring our previous example, we define the 

User
 model in a separate file:

shared/user.js:

export class User {
  constructor({firstName, lastName}) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  getFullName() {
    return `${this.firstName} ${this.lastName}`;
  }
}

Then, we subclass it to implement the views of the web app:

web/user.js:

import {User as BaseUser} from '../shared/user.js';

class User extends BaseUser {
  View = () => {
    return <div>{this.getFullName()}</div>;
  };
}

And the same goes for the iOS app:

ios/user.js:

import {User as BaseUser} from '../shared/user.js';

class User extends BaseUser {
  View = () => {
    return <Text>{this.getFullName()}</Text>;
  };
}

Physically, the code is then a bit more scattered, but logically, it is not. Whichever the platform, from a 

User
 instance, we have access to both the model (
user.firstName
) and its views (
<user.View />
).

Composition over Inheritance

« Inheritance is evil. »
« Composition is the way to go. »

I'm tired of hearing that all the time about anything and everything.

Yes, single inheritance in static languages (Java, C#, etc.) may not be the right approach for composing the multiple pieces of an application. But it is not true with JavaScript where inheritance is dynamic, and therefore, extremely flexible.

For example, we can use mixins to enable any kind of inheritance: multiple, conditional, parameterized, etc.

There are many ways to implement mixins in JavaScript, but there is only one good way, and it is incredibly simple. Please head over here for a nice explanation.

Conclusion

I tried the object-oriented approach when implementing the RealWorld example with Liaison, and I think it worked pretty well. Encapsulating the views into the models made the code a lot more cohesive than if the views were implemented separately.

If you are skeptical (you should be), please have a look at the code and tell me what you think.

Since most of the models are implemented in the backend, the frontend models are pretty much just composed of views.

Some might think that the classes are a bit crowded. I guess it is a matter of taste. Personally, as long as the content is related, I don't mind large files. If you prefer small files, you can group some views into mixins and assemble them into a single model.

This article was originally published on the Liaison Blog.