Is DevRel a Role For You?
Are you a developer who loves to build relationships with developer communities? Are you looking for a transition from a coding role to one that involves developer relations? In a conversation with Rahul Chopra, Niraj Sahay and Abbinaya Kuzhanthaivel of OSFY, Prashanth Subrahmanyam, developer advocate at Google, sheds light on this hot role with some practical advice to help you kick-start your journey. Q . What does the term ‘developer relations’ mean?
A. Developer relations is a term that is probably overused or has different interpretations. There are cases where it’s aligned to the sales or marketing department in different companies. Some of them even term it as developer evangelism.
At Google, ‘developer relations’ is a part of a larger core engineering team. We work closely with engineering teams and with product management teams. We are expected to be technical experts in specialised topics and products, and are uniquely positioned to work closely with the developer community.
We talk to developers and try to get their feedback. I am a developer advocate for Google Cloud and we try to understand how developers view Google Cloud products and how they use them. What is it that they are looking for? What is their feedback about the Google products they are using? What are some use cases they work on, and how difficult or easy is it for them to use these products? What are the likes and dislikes? And so on…
We work both ways. We also give feedback to the developers based on their inputs. What are they finding issues with? What do they want to know more about? What will help them in their usage of Google tools and Google products? This is the kind of information we give back. It could be in the form of articles, blogs, videos, code snippets, and architecture documents, or maybe connecting with other people who have been in this space and are ready to share their experiences to benefit the larger community.
This two-way scenario becomes important because we are the zeroth customer for engineering teams. And as the product is being built, we act as advocates on behalf of developers for the product team work. We try to use the new features that have been released and channel the feedback to product management and engineering.
Q . What are the skills a developer advocate needs?
A. Looking beyond the necessary tech skills, I think empathy is the most important soft skill for developer relations.
Google Cloud is not one single product, but it encapsulates a whole suite of products. There is no single tech or list of skills required. As a developer advocate, I could select a product like Google Kubernetes Engine (GKE), for instance, and create videos and documentation on how it is built and how it should be used. Then the developers are forced to use it the way I am telling them to use it. This doesn’t cut it. Hence, when I say empathy is important, I need to understand what the developers want to do with the product and how it will be used further. Hence, empathy is crucial.
With respect to technology skills, I will start by picking (say) three technologies and deep dive into those. For example, I personally enjoy containers, serverless, and AI/ML. We have a lot of serverless products in Google Cloud, and many that are available from other cloud vendors.
If I just create tutorials on how to use the serverless products, it is probably not enough. I need to dig deep into my topic; for example, why should somebody use serverless?
I need to help users come to serverless given where they currently are. If I am a desktop developer, why is serverless important for me? If I am an on-premise customer, why is serverless important for me? If I want to move to serverless, how can I do that? What are the steps to do it? Does it take six weeks or a year? This is the depth in technology that is expected from someone in developer relations.
One should take up a few areas that one is passionate about and delve deep into them — how will somebody use these, how can someone that wants to use them get to the desired state, and so on.
Q . How has the developer relations role evolved over time?
A. Developer relations has been a core part of Google. For consumer products, we typically do user research and arrive at a good experience. However, there are products that don’t hit end users directly, but somebody in between (developer) has to use the product and build something for end users. This is the way Google Cloud, Android platform, etc, have been developed.
Q . How do developer advocates maintain authenticity when balancing the needs of their company with that of the tech community?
A. To ensure balance, developer relations is a part of the engineering teams. When I am talking to developers, explaining something, I am helping them understand the space and what the product can do. So, for example, we work towards identifying the need - why they want containers and what is the problem statement they are trying to solve. Are containers really important for them? If not, we stop the conversation there.
For example, maybe what they are doing actually needs compute/VMs and not containers. We educate developers and help them understand what they need. We make people love our product and that’s the way we approach any query.
Q . How is the success of a developer advocate measured?
A. We have to show our impact and the value we are bringing in. On one hand, there is the external feedback we get from developers. When we create articles or videos, the number of views is an important metric to note that people are consuming our content. We also get requests for doing sessions, events and community meetups, which indicate the demand for a product; these are measurable numbers.
On the product side, we try to help improve the product. The feedback that is accepted by the engineering or the product team is the impact we create on the product, and is measured as our success.
There are various ways to measure this. For example, some products have a Net Promoter Score. The more people want to interact and work with us, it indicates we are contributing something valuable.
Q . If one part of your role is interfacing between the outside world and the engineering team, does it give you the power to suggest the tech stack for the latter?
A. I wouldn’t call it a tech stack. We help influence the external interface of the product at the level it is going to be consumed. If you look at an API or the SDK, we will influence how the community wants to use it. For example, say there is a product where we do the steps in the order of A-B-C, but find that the world outside is probably using the flow as A-C-B.
“We work closely with engineering teams and with product management teams. We, as developer advocates, are expected to be technical experts in specialised topics and products. We are uniquely positioned to work closely with the developer community.”
So we give that feedback -- that developers may want to use it in this way and we should allow our product to be used in the same way too. We should not say that the Google way is the only way to use the product. This is what we influence.
The engineering team will choose what tech stack it feels is the best for that particular problem and product, for example, Java, Python, frameworks, containers, etc.
Google is very cognizant about security and privacy and we ensure that every library we use -- third party or open source -- is vetted, secure and has gone through all the penetration testing and scans.
The development team has a choice from this buffet of approved libraries and languages, and is free to choose whatever it wants. So, as developer advocates, we don’t get involved with this, but do help ensure the final product is successful.
Q . What are the biggest challenges you have faced as a DevRel expert?
A. The scaling is definitely one. India has so many developers and reaching out to everyone is impossible, but it is important to help users. We expect feedback from developers and would like them to raise issues that need to be addressed. That kind of feedback definitely helps us to fine tune the way we do things. We also definitely want people to realise that we are here to help them understand the topic. That’s basically where I am stepping in and trying to help out. It’s not a very difficult thing, but the more effort we put in and the more we connect with developers, the better it will be.
Q . What is the path for career growth in a DevRel role?
A. Different companies have modelled the role differently. DevRelers are expected to be technical. They need to know their products. People who are experts in containers, Kubernetes and building systems with containers could probably go into a solutions architect kind of role in that space, depending on how deep they are into it. But if somebody was just creating client libraries, that person may not have got the needed exposure in architecture and designing systems. So a solutions architect may not be the right path in that case.
“If you enjoy teaching, maybe you can start your own consulting firm and teach people. There are lots of interpretations of what DevRelers do, but there are definitely multiple paths that you can grow towards.”
There are multiple streams of growth. One is the sales engineering side, where you are spending time, working with people, learning their problems, and explaining technology.
At Google, we are expected to be really hands-on and have technical depth, and hence we are close to technology and code. In such cases, growing as an engineer can also be a career path.
Because you are influencing the product - understanding the user’s needs and impacting the way the product should be built - product management is another possible path.
If you enjoy teaching, maybe you can start your own consulting firm and teach people. There are lots of interpretations of what DevRelers do, but there are definitely multiple paths that you can grow towards.
And within the DevRel role itself, one can expand the scope of impact.
For instance, if I did something for
India and it worked out, I could probably take it to other regions and make this more global. So that way I am growing in the role itself, by increasing the scope of my influence.
Q . How can one move into a DevRel role from an engineering function?
A. If somebody is an extrovert, it’s a great role to move into, but may not be as conducive for someone who is extremely introverted. For example, if someone has stage fear, events can be avoided. But there are many other ways one can work with developers, like writing articles, blogs, or maybe recording a YouTube video.
Communication and empathy are critical across all roles. A frustrated user may tell me a product is terrible. But if I go to my product managers and say this product isn’t good, they will ask me for more information on what happened. What failed? What did the user want to do with the product? So there’s a way in which I need to give feedback too. I put it up to my team stating that these are some of the things that are working the way the product has been built, but are probably different from the way the user expected them to work. Maybe this feature is not reliable. Are we monitoring this? What is our expectation in terms of reliability? All this needs good internal and external communication skills in oral or written forms.
The other aspect is the intention to spread knowledge. If you want to educate developers then you should take up such a role. This was important for me and that’s what led me into this DevRel journey. I have grown as a software architect and as a tech lead. I mentor developers and engineers to ensure that they know how to use technology and match it to problem statements and requirements.
The complete interview is available online at opensourceforu.com
For many, microservices are associated with Web applications, cloud services or at least REST API. Though there is a basis for such an impression, microservices need not always be designed in that way.
What is a microservice?
The basic premise of a microservice is that (in the words of Jeff Bezos of Amazon): “A team that can be fed with just two pizzas should be able to handle a microservice.”
A monolith is a relatively large and complex application that is developed and deployed as just one unit, as shown in Figure 1. It offers multiple functionalities and that’s why it tends to be huge, heavy and rigid. For example, it may be in the form of a single large WAR deployed on a Tomcat
Web server. When the load on the server increases, the performance of the monolith falls. Also, adding new features to a monolith is a daunting and time-taking task.
When a monolith is decomposed into a set of smaller microservices, they evolve independently of each other. The microservices are usually aligned with the lines of business of the organisation. Each microservice covers only a specific business
Designing applications around microservices architecture decomposes a monolith into a set of smaller and collaborative services. The icing on the cake is that we can evolve and scale each of the microservices independently of each other. This transition leverages the strong foundation laid by the objectoriented and domain-driven design patterns. This fifth part of ‘The Design Odyssey’ series demonstrates the evolution of a user management system (UMS) into a bunch of microservices.
requirement. It corresponds to the concept of a sub-domain in the domain-driven design. It will have a domain model specific to its sub-domain. As we all know, a smaller domain model is easy to understand, develop and deploy.
Each team can choose the best platform suitable to develop its specific microservice; yet the services at runtime collaborate with each other to deliver the business value. New services can be added easily and deprecated services can be decommissioned effortlessly.
These services may be exposed via a REST interface, messaging interface or any other interface to the Web clients, mobile clients, IoT clients or standalone clients.
When the microservices are containerised, orchestrated and deployed on cloud infrastructure, they can easily be replicated to scale horizontally!
Service decomposition
The first step in the journey towards microservices is to decompose the monolith. A careful inspection of the monolith gives us an idea of how to peel the slices of functionality from it and convert them into independent microservices.
The user management service (UMS) that we developed as a monolith in the previous parts of this series offers three primary functionalities: 1) To add users to the system; 2) To find a specific user in the system; and 3) To search for users based on some criteria. Apart from these, the UMS is also logging the user-addition events in a central place.
Let’s name them AddService, FindService, SearchService and JournalService. Refer to Figure 2.
Once the UMS is decomposed into the above four smaller microservices, each of them can be developed on different platforms in case such a polyglot environment is beneficial. For instance, AddService may be developed using Spring
Boot on Java platform, FindService may be developed using Express on Node platform, and SearchService may be developed using Flask on Python platform.
The point worth noting is that these services are developed independently, even if all of them are developed on the same platform. The AddService might be released first, followed by the FindService. They do not share any codebase. Code reusability takes the back seat and service independence occupies the driving seat!
AddService
It’s time to design the microservice for adding users into the system. The design still sticks to the fundamental principles of object-oriented design. In other words, we will follow the SOLID principles and use patterns like factories, adapters, etc.
By taking inspiration from the Onion model, which we have seen in the past as part of domain-driven design, AddService is also modelled on the lines of domain layer, application layer and infrastructure layer. The domain layer consists of only domain objects. The application layer acts as the gateway to the domain layer. And the infrastructure layer offers all the generic services.
The domain model
This layer primarily consists of entities, value objects and repositories. The domain model is presented in Figure 3.
As explored in the previous part, an entity is a domain object that is uniquely identifiable and mutable. What else other than the user can be an entity in the domain of AddService? Though the name of a user is not unique in the real world, for the sake of simplicity, let us treat it as the identity of the user. The identity must be immutable, but the rest of the entity can be mutable. For instance, the phone number of the user can be changed at any time.
Unlike the entities, value objects do not have any unique identity and are comparable only by their values. Usually, the primitive types with some sort of constraints are modelled as value objects. For example, the name of the user is modelled as a value object Name, since it is a string with some constraints like minimum length, maximum length, accepted characters, etc. Similarly, the phone number also can be modelled as a value object PhoneNumber, since it is a long number with constraints like length, positivity, etc.
Since the value objects are immutable, they do not offer any way to change their internals. In other words, they only offer getters, but not any setters.
A repository acts as if it is a cache of aggregates of domain objects. The UserRepository is one such repository that saves the user objects. Though the repository belongs to the domain model, the implementation is technology-specific. It may be implemented around MongoDB, MySQL, Cassandra or any other data storage technology. Since the domain does not depend on the technology, the repository implementation UserRepositoryImpl is kept out of the domain model.
The following is the implementation of the domain model on Java platform. The Name, being a value object, offers only a constructor and getters. The constructor may validate the input parameter for constraints before accepting. In case of an invalid parameter, an exception may be thrown. Also, since a value object is only compared with other objects by their values, the hashCode() and equals() methods are required to be implemented.
public class Name { private String value;
public Name(String value) { //TODO: validation this.value = value;
}
public String getValue() {
return this.value; }
@Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((value == null) ? 0 : value. hashCode()); return result;
}
@Override public boolean equals(Object obj) { if (this == obj)
return true; if (obj == null)
return false; if (getClass() != obj.getClass())
return false;
Name other = (Name) obj; if (value == null) { if (other.value != null)
return false;
} else if (!value.equals(other.value))
return false; return true;
}
}
Like in the case of Name, the PhoneNumber is also equipped with constructor, getters, hashCode() and equals() methods, as it is also a value object. Here as well, the constructor may validate the input value.
public class PhoneNumber { private long value;
public PhoneNumber(long value) { //TODO: validation this.value = value; }
public long getValue() { return this.value; }
@Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + (int) (value ^ (value >>> 32));
return result; }
@Override public boolean equals(Object obj) { if (this == obj)
return true; if (obj == null)
return false; if (getClass() != obj.getClass())
return false;
PhoneNumber other = (PhoneNumber) obj; if (value != other.value)
return false; return true;
}
}
The User is an entity! Hence, apart from getters, the setters are also considered. However, as the identity is supposed to be immutable, there is no setter for the Name property.
package com.glarimy.ums.domain;
public class User { private Name name; private PhoneNumber phone; private Date since;
public User(Name name, PhoneNumber phone) { this.name = name; this.phone = phone; this.since = new Date();
}
public User(Name name, PhoneNumber phone, Date since) { this.name = name; this.phone = phone; this.since = since;
}
public Name getName() { return name; }
public PhoneNumber getPhone() { return phone; }
public void setPhone(PhoneNumber phone) { this.phone = phone;
public Date getSince() { return since;
Following is the simple InvalidUserException class that merely extends the system defined Exception class:
package com.glarimy.ums.domain;
@SuppressWarnings(“serial”) public class InvalidUserException extends Exception{
public InvalidUserException() { super();
// TODO Auto-generated constructor stub }
public InvalidUserException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
// TODO Auto-generated constructor stub
public InvalidUserException(String message, Throwable cause) { super(message, cause);
// TODO Auto-generated constructor stub
public InvalidUserException(String message) { super(message);
// TODO Auto-generated constructor stub
public InvalidUserException(Throwable cause) { super(cause);
/ TODO Auto-generated constructor stub
The UserRepository interface just offers the contract for the repository. Since the AddService only saves the user data, the repository is sufficient to offer only that one operation.
package com.glarimy.ums.domain; public interface UserRepository { public User save(User user);
The UserRepositoryImpl somehow saves the user data using technology-specific implementation. For example, it may use JDBC to save data in an RDBMS. We will discuss the data layer at length in future.
package com.glarimy.ums.data;
import com.glarimy.ums.domain.User; import com.glarimy.ums.domain.UserRepository;
public class UserRepositoryImpl implements UserRepository {
@Override public User save(User user) { // TODO return user;
The application layer
This layer consists of DTOs, data mappers, and controllers. The application layer acts as the entry point to the domain service. In other words, it accepts the user requests and passes them to the domain layer.
In the case of AddService, the only kind of request that is accepted is to add a new user. The UserController offers the add() method for this purpose. It accepts a NewUser as the parameter and returns UserRecord. Both the NewUser and UserRecord are DTOs. Refer to Figure 4.
A DTO is just a data structure useful for carrying the data between the domain layer and the client. It does not carry any domain logic.
Following is the implementation of the application layer. Being a DTO, the NewUser is a simple data structure. The DTOs do not apply any business rules or logic. Hence it is OK to make the attributes public.
package com.glarimy.ums.app;
public class NewUser { public String name; public long phone;
public NewUser(String name, long phone) { this.name = name; this.phone = phone;
Like NewUser, the UserRecord is also a DTO. The implementation is self-explanatory:
package com.glarimy.ums.app; import java.util.Date;
public class UserRecord { public String name; public long phone; public Date since;
public UserRecord(String name, long phone, Date since) { this.name = name; this.phone = phone; this.since = since;
}
And, finally, the UserController is the one that connects with the domain model to serve the user requests. It may also connect with the other infrastructure like message brokers, email servers, etc.
These kinds of classes are popularly called Controller classes largely due to the influence of the MVC pattern and Spring framework. For that matter, they can be named as you wish. However, these classes follow a simple process in serving the user requests.
Controllers map the input DTO to appropriate domain objects, invoke operations on domain objects, convert the results back into DTOs and return them to the caller.
Notice that the domain objects are never leaked beyond the application layer and the domain layer never entertains DTOs.
The UserController also follows the same pattern: package com.glarimy.ums.app;
import com.glarimy.broker.Event; import com.glarimy.broker.Publisher; import com.glarimy.ums.domain.Name; import com.glarimy.ums.domain.PhoneNumber; import com.glarimy.ums.domain.User; import com.glarimy.ums.domain.UserRepository;
public class UserController { private UserRepository repo;
public UserController(UserRepository repo) { this.repo = repo;
public UserRecord add(NewUser newUser) {
Name name = new Name(newUser.name);
PhoneNumber phoneNumber = new PhoneNumber(newUser.phone); User user = new User(name, phoneNumber);
user = repo.save(user);
Event event = new Event(“”, “”); Publisher publisher = new Publisher(); publisher.publish(event);
UserRecord record = new UserRecord(user.getName().getValue(), user.getPhone().getValue(), user.getSince());
return record; }
}
The UserController is also supposed to handle the domain exceptions, though it is ignored in the above-illustrated code. Another point that can be noticed in the code is that it is also connected to BrokerService for firing the events for journaling.
That’s all! Any microservice of this nature consists of such domain and application layers, at a minimum. All the remaining technical stuff is handled by the infrastructure layer.
Infrastructure layer
This layer consists of databases, message brokers, directories, file servers, email servers…and the list just goes on. The infrastructure is not specific to a domain, which is obvious. It is more technical in nature and may be available as frameworks, libraries, etc, off the shelf.
In our case, AddService is using BrokerService and a set of Framework classes. Let’s briefly explore them as well.
The BrokerService: This is an infrastructure service that offers a way for publishers and subscribers to stay in touch with each other, asynchronously. Publishers publish events to the broker and subscribers listen for the events. A full-scale broker design is not covered at this point. Just its API classes are good enough for the publishers and subscribers like AddService. Normally, these kinds of infrastructure services are offered by third-party tools like Kafka. In our case, we are building these in-house.
Our BrokerService offers an API that consists of Event and Publisher, as presented in Figure 5.
package com.glarimy.broker;
public class Event { private String topic; private String message;
public Event(String topic, String message) {
//TODO: validation this.topic = topic; this.message = message;
}
public String getTopic() { return topic;
}
public String getMessage() { return message;
}
And the actual implementation of the publisher is not that important for us, at this point!
package com.glarimy.broker;
public class Publisher { public void publish(Event event) { //TODO
Generic framework: The AddService and many other microservices require some sort of framework to handle the common technical work. For instance, the framework may offer factories, builders, exception stations, proxies, decorators, and what not? Again, such frameworks are available in the market. In our case, we are building the framework as well. The simple model of our framework is presented in Figure 6.
Since the framework is generic, it belongs to the infrastructure and many microservices (not just AddService) may want to use it.
Thus we designed AddService as a microservice, and BrokerService and Framework as reusable infrastructure services.
FindService
Once you design a microservice, you can design any number of microservices. Just like in the case of AddService, the FindService also designs relevant domain models, application layers and uses some technical services that form the infrastructure layer.
Observe Figure 7. You will notice that the entities and value objects of the domain model are more or less similar to that of the AddService. This happens in any microservice. Yet, the teams may not even know about it as they work fairly independently on their own platforms. For example, while AddService is implemented on Java, FindService may be implemented on Python.
Figure 8 depicts the application layer of FindService. This also follows the same pattern as we have seen in the case of AddService.
Since we have seen the code for AddService, there is no point in illustrating the code for the remaining services. It saves both time and space.
SearchService
By now it must be clear that designing these kinds of
microservices follows this pattern: 1) Domain model with entities, value objects and repositories, 2) Application layer with controllers and DTOs, and finally, 3) Infrastructure layer with a whole set of generic technical services.
Figure 9 presents the domain model of SearchService and Figure 10 depicts the application layer.
JournalService
And finally, the fourth microservice of the UMS is to listen to the message broker for the events and log them to a centralised journal. Being a separate microservice, this will be developed and deployed independently of the other services.
Refer to the previous parts of this series, for the way we designed journaling in monolith UMS. The design of JournalService has not changed much except for the fact that it is now an independent service. Figure 11 presents the domain model.
Perspective
We have just started our journey into the world of microservices. We have refactored the UMS as a collection of four microservices and presented their models. As mentioned at the beginning of this article, the services may be developed on a common platform or on different platforms. However, they are ultimately deployed on a microservices infrastructure.
What does that infrastructure look like? How about data management across the microservices? How do these services collaborate with each other? How are these services actually developed and deployed? What is the role of Dockers? What is the role of Kubernetes?
Well, we will solve these puzzles in the next several parts.