1. 程式人生 > >end developer should know publish

end developer should know publish

Let’s do (and break) something

Consider a simple application, a really simple one. For instance, a small app for marking our favourite places on a map. Nothing fancy there: just a map viewon the right and simple sidebar on the left. Clicking on a map should save a new marker on a map.

Of course, we’re a little ambitious and we’re going to make additional feature: we want it to remember the list of places using local storage.

Now, basing on that description, we can draw some basic action flow chart for our app:

As you can see, it isn’t going to be complicated.

For brevity, following examples are going to be written without using any framework nor UI library — only vanilla JavaScript is involved. Also, we’re going to use small part of Google Maps API — if you want to create similar app yourself, you should register your API key on
https://cloud.google.com/maps-platform/#get-started
.

Okay, let’s get down to coding and create a quick prototype:

Let’s quickly analyze this one:

  • init() function initializes the map element using Google Maps API, sets up the map click action and then tries to load markers from the localStorage.
  • addPlace() handles the map click — then adds new place to the list and invokes marker rendering.
  • renderMarkers() iterates through the places in the array and after clearing the map, puts the markers on it.

Let’s put aside some imperfections here (monkey patching, no error handling etc.) — it will serve good enough as the prototype. Neat. Let’s build some markup for that:

Assuming that we have some styles attached (we won’t cover it here since it’s not relevant), believe me or not — it actually does it job:

Despite being ugly, it works. But is not scalable.Uh-oh.

First of all, we’re mixing responsibilities here. If you’ve heard aboud SOLID principles, you should already know that we’re already breaking the first of them: Single Responsibility Principle. In our example — despite its simplicity — one code file is taking care of both handling the user actions and dealing with data and its synchronization. It shouldn’t. Why? “Hey, it works, right?” — you might say. Well, it does, but its hardly maintainable in terms of next functionalities.

Let me convince you in other way. Imagine that we are going to extend our app and add new features there:

First of all, we want to have a list of marked places in the sidebar. Second, we want to lookup city names by Google API — and this is where asynchronous mechanisms are introduced.

Okay, so our new flowchart will have following shape:

Note: city name lookup is not rocket science, Google Maps provide really simple API for that. You can check it out yourself!

There is a certain characteristic of getting the city name from the Google API: it’s not instant. It needs to call proper service in the Google’s JavaScript library, and it will take some time for the response to come back. It will cause a little complication — but surely an educational one.

Let’s go back to the UI and try to notice one apparent thing. There are two supposedly separate interface areas: the sidebar and the main content area. We definitely should not write one big piece of code that handles both. Reason is obvious — what if in the future we will have four components? Or six? Or 100? We need to have our code divided into chunks — this way, we will have two separate JavaScript files. One for the sidebar, second for the map. And the question is — which one should keep the array with the places?

Which approach is correct here? Number one or number two? Well, the answer is: neither one. Remember single responsibility principle? In order to stay clean and modular (and cool) we should somehow separate concerns and hold our data logic somewhere else. Behold:

Holy grail of code separation: we can move the storage and logicinto another code file, which will centrally deal with the data. This file — the service — will be responsible for the those concerns and mechanisms like synchronization with local storage. Contrary, components will only serve as interface parts. SOLID as it should be. Let’s try to introduce this pattern:

Service code:

Map component code:

Sidebar component code:

Well, great part of the itch is gone. Code is neatly organized in proper drawers again. But before we start feeling good again, let’s run this one.

…oops.After doing any action, we can’t the interface is not reacting.

Why? Well, we didn’t implement any means of synchronization. After adding a place using imported method, then we’re not signalled about it anywhere. We can’t even put the getPlaces() method in the next line after invoking addPlace(), because city lookup is asynchronous and takes time to accomplish.

Things are happening in the background, but our interface is not aware of its results — after adding a marker on the map, we’re not seeing the change in the sidebar. How to solve it?

A pretty simple idea is to poll our service once in a while — for instance, every component could get items from the service every second. E.g.:

Does it work? Choppy, but yes. But is it optimal? Not at all — we’re flooding event loop of our app with actions that won’t have any effect in majority of cases.

After all, you’re not visiting your local post office every hour to check if the package have arrived. Similarly, if you’re leaving our car for repairs, youdon’t call the mechanic every half hour to ask if the job is done (at least hopefully you’re not this kind of person). Instead, we’re waiting for a phone call. And when the job is ready, how does the mechanic know who to call? Silly — of course, we have left him our contact data.

Now, let’s try to mimic “leaving our contact data” in the JavaScript.