Knots - the Lazy Data Transfer Objects for Dealing with the Microservices Craze

39
Knots – the Lazy Data Transfer Objects for Dealing with the Microservices Craze

Transcript of Knots - the Lazy Data Transfer Objects for Dealing with the Microservices Craze

Page 1: Knots - the Lazy Data Transfer Objects for Dealing with the Microservices Craze

Knots – the Lazy Data Transfer Objects for Dealing with the

Microservices Craze

Page 2: Knots - the Lazy Data Transfer Objects for Dealing with the Microservices Craze

[ashopov@ashmac ~]$ whoami

By day: Software Engineer at UberBy night: OSS contributorCoordinator of Bulgarian Gnome TPGit, bash, Sentry, Jenkins speak Bulgarian

Contacts: E-mail: [email protected] LinkedIn: http://www.linkedin.com/in/alshopov SlideShare: http://www.slideshare.net/al_shopov GitHub: https://github.com/alshopov Web: Just search “al_shopov”

Page 3: Knots - the Lazy Data Transfer Objects for Dealing with the Microservices Craze

Please Learn and Share

License: Creative Commons Attribution 4.0 International

(CC-BY v4.0)

Page 4: Knots - the Lazy Data Transfer Objects for Dealing with the Microservices Craze

The Whole Lecture in One Slide

// KNOTpublic class UserKnot { private final int userId; private User user;

public UserKnot(int userId) { this.userId = userId; }

public int getUserId() { return userId; } public User getUser() { if (user == null) { user = USER_SERVICE. getUserById(userId); } return user; }}

// BEAN-ish, no no-args constr.public class UserBean { private int userId; private User user;

public UserBean(int userId, User user) { this.userId = userId; this.user = user; }

public int getUserId() { return userId; } public User getUser() { return user; }}||

Page 5: Knots - the Lazy Data Transfer Objects for Dealing with the Microservices Craze

What Did Microservices Give Us?

● Many services:– Bigger than micro (geodes)– Lesser than micro (nanoservices)

● Every solution is another micro service:– Did we have a problem?– Was it the right problem?

Page 6: Knots - the Lazy Data Transfer Objects for Dealing with the Microservices Craze

Fundamental Theorem of Software Engineering

All problems in computer science can be solved by

another level of indirection, except of course for the

problem of too many indirections

Page 7: Knots - the Lazy Data Transfer Objects for Dealing with the Microservices Craze

Fundamental Theorem of Software Practice

All problems in a microservices architecture

can be solved by other microsrvices, except of

course for the problem of too many microservices.

Page 8: Knots - the Lazy Data Transfer Objects for Dealing with the Microservices Craze

What Did Microservices Take Away From Us?

?

Page 9: Knots - the Lazy Data Transfer Objects for Dealing with the Microservices Craze

Dude, Where Are My JOINs?

● Data relates to other data.● You may denormalize but you cannot have all

microservices have all the data● Data is isolated in domains, different microservices

serve it and you have to re-join it● Single source of truth? What do you mean by truth?● A whole workflow is like a quest – Raiders of the Lost

Join – you go to different services, ask questions and get answers– What order?– How many times?

Page 10: Knots - the Lazy Data Transfer Objects for Dealing with the Microservices Craze

micro SERVICES● No matter how big they are, they are services● They are at least a network call away● Money cannot buy time!● Money can buy memory, servers, disks, more

bandwidth, engineers● 299 792 458 m / s – it is the law. Even in

Pernik!

Page 11: Knots - the Lazy Data Transfer Objects for Dealing with the Microservices Craze

DRY, KISS, YAGNI for Microservices

● Do not repeat your queries for the same data if you can avoid it– Once you get the data – keep it

● Keep this avoiding simple– There are many services, you cannot pass

the data of all of them as arguments in all combinations

● If you do not need some data – you ain’t gonna need it– Load as lazily as you can

Page 12: Knots - the Lazy Data Transfer Objects for Dealing with the Microservices Craze

Keep on Adding, Pass it All Around

● Through layers ● Through modules

Page 13: Knots - the Lazy Data Transfer Objects for Dealing with the Microservices Craze

This Is the Essence of Tying a Knot

// KNOTpublic class UserKnot { private final int userId; private User user; public UserKnot(int userId) { this.userId = userId; } public int getUserId() { return userId; }

public User getUser() { if (user == null) { user = Registry. getInstance(). getUserService(). getUserById(userId); } return user; }}

DRY – the second call to getUserdoes not repeat the request

KISS – getUser hides specificsservice are behind a getter

YAGNI – if you never call getUser –you will not incur a network call

Page 14: Knots - the Lazy Data Transfer Objects for Dealing with the Microservices Craze

This Is the Essence of Tying a Knot

// KNOTpublic class UserKnot { private final int userId; private User user; public UserKnot(int userId) { this.userId = userId; } public int getUserId() { return userId; }

public User getUser() { if (user == null) { user = Registry. getInstance(). getUserService(). getUserById(userId); } return user; }}

DRY – the second call to getUserdoes not repeat the request

KISS – getUser hides specificsservice are behind a getter

YAGNI – if you never call getUser –you will not incur a network call

SELECT * FROM users AS u WHERE u.id=42;

Page 15: Knots - the Lazy Data Transfer Objects for Dealing with the Microservices Craze

Extendable – Direct Joins

public class ExtendedUserKnot { private final int userId; private User user;

private List<Account> accounts;

public ExtendedUserKnot(int userId) { this.userId = userId; } public int getUserId() { return userId; }

public User getUser() { if (user == null) { user = USER_SERVICE.getUserById(userId); } return user; } public List<Account> getAccounts() { if (accounts == null) { accounts = ACCOUNT_SERVICE.getAccountByUserId(userId); } return accounts; }}

Join another service

Page 16: Knots - the Lazy Data Transfer Objects for Dealing with the Microservices Craze

Extendable – Direct Joins

public class ExtendedUserKnot { private final int userId; private User user;

private List<Account> accounts;

public ExtendedUserKnot(int userId) { this.userId = userId; } public int getUserId() { return userId; }

public User getUser() { if (user == null) { user = USER_SERVICE.getUserById(userId); } return user; } public List<Account> getAccounts() { if (accounts == null) { accounts = ACCOUNT_SERVICE.getAccountByUserId(userId); } return accounts; }}

Join another service

SELECT * FROM users AS u JOIN accounts AS a ON u.id=a.user_id WHERE u.id=42;

Page 17: Knots - the Lazy Data Transfer Objects for Dealing with the Microservices Craze

Extendable – Multiple Joins

public class DoubleUserKnot { private final int userId;userId; private final int bankId; private User user; private Bank bank; private accounts;

public DoubleUserKnot(int userId, int bankId) { this.userId = userId; this.bankId = bankId; } public int getUserId() { return userId; } public User getUser() { if (user == null) { user = USER_SERVICE.getUserById(userId); } return user; }

public List<Account> getAccounts() { if (accounts == null) { accounts = ACCOUNT_SERVICE. getAccountByUserIdBankId(userId, bankId); } return accounts; } public Bank getBank() { if (bank == null) { bank = BANK_SERVICE.getBankById(bankId); } return bank; }}

Capture several attributes

Join many services

Service depends on several attributes

Page 18: Knots - the Lazy Data Transfer Objects for Dealing with the Microservices Craze

Extendable – Multiple Joins

public class DoubleUserKnot { private final int userId;userId; private final int bankId; private User user; private Bank bank; private accounts;

public DoubleUserKnot(int userId, int bankId) { this.userId = userId; this.bankId = bankId; } public int getUserId() { return userId; } public User getUser() { if (user == null) { user = USER_SERVICE.getUserById(userId); } return user; }

public List<Account> getAccounts() { if (accounts == null) { accounts = ACCOUNT_SERVICE. getAccountByUserIdBankId(userId, bankId); } return accounts; } public Bank getBank() { if (bank == null) { bank = BANK_SERVICE.getBankById(bankId); } return bank; }}

Capture several attributes

Join many services

Service depends on several attributes

SELECT * FROM users AS u JOIN accounts AS a ON u.id=a.user_id JOIN banks AS b ON u.id=a.bank_id WHERE u.id=42 AND b.id=666;

Page 19: Knots - the Lazy Data Transfer Objects for Dealing with the Microservices Craze

Composable – Knot Within a Knot

public class CountryKnot { private final int id; private Country country; private Currency currency; public CountryKnot(int id) { this.id = id; } public int getId() { return id; } public Country getCountry() { if (country == null) { country = COUNTRY_SERVICE. getCountryById(id); } return country; } public Currency getCurrency() { if (currency == null) { currency = CURRENCY_SERVICE. GetCurrencyById( getCountry(). getCurrencyId()); } return currency; }}

public class ComposedUserKnot { private final int userId; private User user; private CountryKnot countryKnot; public ComposedUserKnot(int userId) { this.userId = userId; } public User getUser(){ if (user == null){ user = USER_SERVICE. getUserById(userId); } return user; } public Country getCountry(){ if (countryKnot == null){ countryKnot = new CountryKnot( getUser(). getCountryId()); } return countryKnot.getCountry(); } public Currency getCurrency() { return countryKnot.getCurrency(); }}

Page 20: Knots - the Lazy Data Transfer Objects for Dealing with the Microservices Craze

Composable – Knot Within a Knot

public class CountryKnot { private final int id; private Country country; private Currency currency; public CountryKnot(int id) { this.id = id; } public int getId() { return id; } public Country getCountry() { if (country == null) { country = COUNTRY_SERVICE. getCountryById(id); } return country; } public Currency getCurrency() { if (currency == null) { currency = CURRENCY_SERVICE. GetCurrencyById( getCountry(). getCurrencyId()); } return currency; }}

public class ComposedUserKnot { private final int userId; private User user; private CountryKnot countryKnot; public ComposedUserKnot(int userId) { this.userId = userId; } public User getUser(){ if (user == null){ user = USER_SERVICE. getUserById(userId); } return user; } public Country getCountry(){ if (countryKnot == null){ countryKnot = new CountryKnot( getUser(). getCountryId()); } return countryKnot.getCountry(); } public Currency getCurrency() { return countryKnot.getCurrency(); }}

SELECT * FROM users AS u JOIN countries AS c ON u.country_id=c.id JOIN currency AS cu ON c.currency_id=cu.id WHERE u.id=42;

Page 21: Knots - the Lazy Data Transfer Objects for Dealing with the Microservices Craze

Threads, Anybody?

● A lot of frameworks get a request–response cycle in a single thread, all the layers are in the thread that got the initial data, so no synchronization is needed

● If your knots will be touched by many threads – you need some synchronization.

● First to request – will block until knot is tied.● The rest of requesters – will also have to wait.

– It is rare that you need to issue the same request several times – unreliable network, changing routing, etc.

Page 22: Knots - the Lazy Data Transfer Objects for Dealing with the Microservices Craze

Thread safetyDouble checked

locking

public class ThreadSafeUserKnot { // User MUST be immutable, reference MUST be volatile // userId MUST be final // Every service MUST have own lock private final int userId; private volatile User user; private final Object userLock = new Object(); public ThreadSafeUserKnot(int userId) { this.userId = userId; } public int getUserId() { return userId; } public User getUser() { if (user == null) { synchronized (userLock) { if (user == null) { user = USER_SERVICE.getUserById(userId); }}} return user; }}

Page 23: Knots - the Lazy Data Transfer Objects for Dealing with the Microservices Craze

Lazily Instantiated

● The magic behind knots is that we query external services lazily – not earlier than needed.

● We do not incur network latency if we do not need the network call.

● While the full workflow may need many calls, parts may take decision based on partial information – quick bailout.

● It is easier going from lazy to eager fetches – but more about this when we talk about observables.

Page 24: Knots - the Lazy Data Transfer Objects for Dealing with the Microservices Craze

Facade

● The knots serve as a facade to external services

● You capture request info in constructor● All peculiarities of the different network services

are hidden behind a simple getter.● Easier to use, easier to read

Page 25: Knots - the Lazy Data Transfer Objects for Dealing with the Microservices Craze

Proxy

● Network calls are expensive, so we proxy● Proxying and forwarding allows us to cache the

result per request● Proxying works because we mainly read, thus

we do not need full functioning objects that we can modify

Page 26: Knots - the Lazy Data Transfer Objects for Dealing with the Microservices Craze

Data Transfer Object

● It holds all data● But no need for serialization – a knot is always

local. Crossing microservices frequently means crossing language and framework barriers.

● Similarly – no business logic, but consistency checking and validation are important since data is shared across many services. There is duplication and a knot may check consistency of data.

Page 27: Knots - the Lazy Data Transfer Objects for Dealing with the Microservices Craze

Compared to ORM Entities

● Knot is readonly● Synchronizes once per request● Explicitly shows slow calls● Always starts lazy and predominantly stays so● When knots stop being so lazy they become

parallel● SELECT n+1 – for entities bad performance, for

knots – impossible performance– Entities – joins or lazy– Knots – already lazy, no joins – bulk APIs

Page 28: Knots - the Lazy Data Transfer Objects for Dealing with the Microservices Craze

What About Testing?

● Testing is very easy if you have mocked the services. Knots basically aggregate the objects returned by the mocks

● No business logic – nothing to test● Verification of data from different services

– Very important– Couples with observability

Page 29: Knots - the Lazy Data Transfer Objects for Dealing with the Microservices Craze

Refactoring

● If you have too many services calls per request – make whoever calls you provide you with some of the information you need, you push more in the constructor, you have shorter chain of knots

● If you cannot trust the input and need to get it on your own – you go the other direction

● Knots make both possible and compatible● The rest of refactoring is your microservice. Knots

separate you from changes in other microservices.

Page 30: Knots - the Lazy Data Transfer Objects for Dealing with the Microservices Craze

Observable

● Most often you log network calls and then reconstruct calls, usually you have a request identifier

● Knots allow you to reverse and/or augment this logging

● Do you always call service B after calling service A?– Issue both calls together

● If you call service T to get some data but it is also available elsewhere – stop calling T.

Page 31: Knots - the Lazy Data Transfer Objects for Dealing with the Microservices Craze
Page 32: Knots - the Lazy Data Transfer Objects for Dealing with the Microservices Craze

Knots’ Single Responsibility

● On the one hand they break it because they knot together many services

● On the other – their primary purpose is to minimize network calls

● Make using microservices easier● Transparent yet not abstract

Page 33: Knots - the Lazy Data Transfer Objects for Dealing with the Microservices Craze
Page 34: Knots - the Lazy Data Transfer Objects for Dealing with the Microservices Craze

Open/Closed

● Knots are closed for modification and not extendable

● You can implement an open basic class and do tricks with generics, however this implies very similar microservices which is not true in practice

● There is only so much you can get from extension, sorry

● Knots should be easy to understand and trace, do not be overly creative with them

Page 35: Knots - the Lazy Data Transfer Objects for Dealing with the Microservices Craze
Page 36: Knots - the Lazy Data Transfer Objects for Dealing with the Microservices Craze

Law of Demeter

● Talk to immediate friends, not strangers● Method A.a() calls method B.b() and not

B.c.d.f()● Tell, don’t ask● Break it regarding to knots, you may reach

inside them – they can be arbitrarily nested even though microservices have flatter structure at least initially

● This allows to organize the rest of the codebase to upkeep the law

Page 37: Knots - the Lazy Data Transfer Objects for Dealing with the Microservices Craze

Compare Similar Solutions

● Spring’s Request Scoped Beans– Only Java– Only Spring– Web container targeted

● VMWare’s Xenon– Again Java only– Much larger scope – whole framework– Much tighter integration, your code functions

inside it

Page 38: Knots - the Lazy Data Transfer Objects for Dealing with the Microservices Craze

That’s All, Folks

● Questions?● Anything to declare?● Microservices? To Knot or Not?

Page 39: Knots - the Lazy Data Transfer Objects for Dealing with the Microservices Craze

● Images: Wikimedia Commons