This article is not a boast article, it will not talk about a lot of deep architecture, on the contrary, it will explain a lot of basic problems and writing problems, if the reader thinks that the basic problems and writing problems are not a problem, please ignore this article, save time to do something meaningful.

The development tools

It’s hard to know how many “old” programmers still use Eclipse, who are either stuck in the old ways or don’t even know that other good development tools exist, but Eclipse’s memory lag, and the odd odd occurrence, tell us it’s time to look for new development tools.

If you want to be a good Java programmer, please switch to IntelliJ IDEA. For the benefits of using IDEA, please search Google.

Don’t tell me the shortcut keys are bad. Changing the IDE is not the focus of my article, so I don’t want to spend too much time on why TO change the IDE. Here, I can only tell you that the only reason to change ides is to write Java code better and faster. The reason is slightly. Don’t tell me shortcuts don’t work. Try something new.

Beans Beans are one of our most used models, and I’ll explain beans at length, hoping you’ll get a good feel for it.

In the “experience” of many Java programmers, a database table corresponds to a Domain object, so many programmers write code using package names: Com.xxx. domain, which seems to have become an industry constraint, should be a database mapping object. But you’re wrong, Domain is a Domain object, and often when we do traditional Java software Web development, these domains are anaemic models, they don’t have behavior, or they don’t have enough behavior of Domain model, so in this theory, Each of these domains should be a generic Entity object, not a Domain object, so change the package name to: com.xxx.entity.

If you don’t understand what I’m saying, take a look at a book called IMPLEMENTING Domain-Driven DESIGN by Vaughn Vernon, which explains the difference between an anaemic model and a DOMAIN model.

DTO data transmission we should use DTO objects as the transmission object, this is our agreement, because I have been doing mobile TERMINAL API design work for a long time, many people told me that they think only when transmitting data to mobile terminal (input or output), these objects become DTO objects. Please pay attention! Such understanding is wrong. As long as the objects are used for network transmission, we all think they can be regarded as DTO objects. For example, in the e-commerce platform, when users place an order, the data after placing an order will be sent to OMS or ERP system.

We agree to change the name of an object to XXDTO if it is a DTO object, such as OMSOrderInputDTO.

As we know, A DTO is a model object that the system interacts with the outside world, so there must be a step to convert a DTO a BO object or a normal Entity object, and let the Service layer handle it. Search the public vertical number: MarkerHub, follow the reply [vue] to get the beginning of the front and back end tutorial!

scenario Operations such as adding members, because for demonstration, I only consider the user’s some simple data, when the background administrator click add user, only need to get the user’s name and age, the back-end, after receive the data will be added to create and update time and the default password three fields, then save the database.

@RestController public class UserApi { @Autowired private UserService userService; @PostMapping public User addUser(UserInputDTO userInputDTO){ User user = new User(); user.setUsername(userInputDTO.getUsername()); user.setAge(userInputDTO.getAge()); return userService.addUser(user); }}Copy the code

Let’s just focus on the transformation code in the above code, ignore the rest:

User user = new User();
user.setUsername(userInputDTO.getUsername());
user.setAge(userInputDTO.getAge());
Copy the code

Please use the code above the tool, logically speaking, there is no problem, but this kind of writing annoys me, there are only two fields in the example, if there are 20 fields, what should we do? Do you set data one by one? Of course, if you do this, you’ll be fine, but it’s certainly not optimal.

There are many tools on the web that support shallow copy or deep copy Utils. For example, we can use the org. Springframework. Beans. BeanUtils# copyProperties for code refactoring and optimization:

@PostMapping
public User addUser(UserInputDTO userInputDTO){
 User user = new User();
 BeanUtils.copyProperties(userInputDTO,user);
 return userService.addUser(user);
}
Copy the code

Beanutils.copyproperties is a shallow copy method. To copyProperties, we just need to set the DTO object and the converted object to the same name and ensure the same type. If you’re always using set for attribute assignments while doing DTO conversions, try simplifying your code and making it cleaner!

The semantics of the transformation process, the reader must feel a lot of elegant, but when we write Java code, we need to consider more semantic operations, again look at the above code:

User user = new User();
BeanUtils.copyProperties(userInputDTO,user);
Copy the code

Although this code simplifies and optimizes the code nicely, its semantics are problematic and we need to implement a conversion process, so the code is changed as follows:

@PostMapping
 public User addUser(UserInputDTO userInputDTO){
 User user = convertFor(userInputDTO);
 return userService.addUser(user);
 }
 private User convertFor(UserInputDTO userInputDTO){
 User user = new User();
 BeanUtils.copyProperties(userInputDTO,user);
 return user;
 }
Copy the code

This is a better way of writing semantics. It is more complicated, but it is much more readable. When writing code, we should try to put semantic layers more or less into a method, for example:

User user = convertFor(userInputDTO);
return userService.addUser(user);
Copy the code

Neither code exposes the implementation, but rather shows how to do a set of semantic operations at the same level within the same method, rather than exposing the implementation.

As mentioned above, it’s a way of refactoring, Readers can refer to Extract Method Refactoring in Martin Fowler’s Refactoring Imporving the Design of Existing Code (Refactoring — Improving the Design of Existing Code).

Abstract Interface Definition When we do the DTO conversion of several apis, we will find that there are many such operations, so we should define an interface, so that all such operations have a rule.

If the interface is defined, then the semantics of the convertFor method will change and it will be an implementation class.

Take a look at the abstract interface:

public interface DTOConvert<S,T> {
 T convert(S s);
}
Copy the code

Although this interface is very simple, it tells us one thing: to use generics, if you are a good Java programmer, make generics for the abstract interface you want to make. Let’s look at the interface implementation:

public class UserInputDTOConvert implements DTOConvert {
@Override
public User convert(UserInputDTO userInputDTO) {
User user = new User();
BeanUtils.copyProperties(userInputDTO,user);
return user;
}
}
Copy the code

When we refactor this way, we find that the code is now so concise and so normative:

@RequestMapping("/v1/api/user") @RestController public class UserApi { @Autowired private UserService userService; @PostMapping public User addUser(UserInputDTO userInputDTO){ User user = new UserInputDTOConvert().convert(userInputDTO); return userService.addUser(user); }}Copy the code

If you’re a good Java programmer, I’m sure you’ve reviewed your Code several times, as I have.

The problem is that you shouldn’t return the User entity directly, because if you do that, you’re exposing too much information about the entity. It’s not safe to return a value like that, so instead you should return a DTO object, We can call it UserOutputDTO:

@PostMapping
public UserOutputDTO addUser(UserInputDTO userInputDTO){
 User user = new UserInputDTOConvert().convert(userInputDTO);
 User saveUserResult = userService.addUser(user);
 UserOutputDTO result = new UserOutDTOConvert().convertToUser(saveUserResult);
 return result;
}
Copy the code

So your API is more robust. I don’t know if you notice any other problems after reading this code, but as a good Java programmer, take a look at the code we just abstracted:

User user = new UserInputDTOConvert().convert(userInputDTO);
Copy the code

As you can see, there is no need for a new DTO transform object, and each DTO transform object is generated when it encounters a DTO transform. Therefore, we should consider whether we can aggregate this class and the DTO.

public class UserInputDTO { private String username; private int age; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public User convertToUser(){ UserInputDTOConvert userInputDTOConvert = new UserInputDTOConvert(); User convert = userInputDTOConvert.convert(this); return convert; } private static class UserInputDTOConvert implements DTOConvert<UserInputDTO,User> { @Override public User convert(UserInputDTO userInputDTO) { User user = new User(); BeanUtils.copyProperties(userInputDTO,user); return user; }}}Copy the code

The conversion in the API is then performed by:

User user = new UserInputDTOConvert().convert(userInputDTO);
User saveUserResult = userService.addUser(user);
Copy the code

Becomes:

User user = userInputDTO.convertToUser();
User saveUserResult = userService.addUser(user);
Copy the code

We added the transformation behavior to the DTO object, which I believe makes the code more readable and semantically correct.

Look again at the utility class to see the DTO internal conversion code, it implements our own definition of DTOConvert interface, but this is really no problem, do not need to think again?

Search the public vertical number: MarkerHub, follow the reply [vue] to get the beginning of the front and back end tutorial!

I don’t think so. For conversion semantics like Convert, many utility classes have this definition. Convert is not a business-level interface definition, but a generic interface definition for converting property values between ordinary beans. Therefore, we should read more code with Convert semantics.

I carefully read the source of GUAVA, found com.google.com mon. Base. The Convert such a definition:

public abstract class Converter<A, B> implements Function<A, B> { protected abstract B doForward(A a); protected abstract A doBackward(B b); //Copy the code

GUAVA can Convert both forward and reverse versions of the DTO.

private static class UserInputDTOConvert implements DTOConvert<UserInputDTO,User> { @Override public User convert(UserInputDTO userInputDTO) { User user = new User(); BeanUtils.copyProperties(userInputDTO,user); return user; }}Copy the code

Revised:

private static class UserInputDTOConvert extends Converter<UserInputDTO, User> { @Override protected User doForward(UserInputDTO userInputDTO) { User user = new User(); BeanUtils.copyProperties(userInputDTO,user); return user; } @Override protected UserInputDTO doBackward(User user) { UserInputDTO userInputDTO = new UserInputDTO(); BeanUtils.copyProperties(user,userInputDTO); return userInputDTO; }}Copy the code

After looking at this part of the code, you might ask, what’s the use of reverse conversion? In fact, we have a lot of small business requirements, input and output parameters are the same, so we can easily convert, I mentioned above UserInputDTO and UserOutputDTO to UserDTO show you.

DTO:

public class UserDTO { private String username; private int age; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public User convertToUser(){ UserDTOConvert userDTOConvert = new UserDTOConvert(); User convert = userDTOConvert.convert(this); return convert; } public UserDTO convertFor(User user){ UserDTOConvert userDTOConvert = new UserDTOConvert(); UserDTO convert = userDTOConvert.reverse().convert(user); return convert; } private static class UserDTOConvert extends Converter<UserDTO, User> { @Override protected User doForward(UserDTO userDTO) { User user = new User(); BeanUtils.copyProperties(userDTO,user); return user; } @Override protected UserDTO doBackward(User user) { UserDTO userDTO = new UserDTO(); BeanUtils.copyProperties(user,userDTO); return userDTO; }}}Copy the code

API:

@PostMapping
 public UserDTO addUser(UserDTO userDTO){
 User user = userDTO.convertToUser();
 User saveResultUser = userService.addUser(user);
 UserDTO result = userDTO.convertFor(saveResultUser);
 return result;
 }
Copy the code

Of course, the above only indicates the forward or reverse direction of transformation. Many business requirements have different Dtos for outgoing and incoming parameters, so you need to tell the program more clearly that reverse cannot be called.

private static class UserDTOConvert extends Converter<UserDTO, User> { @Override protected User doForward(UserDTO userDTO) { User user = new User(); BeanUtils.copyProperties(userDTO,user); return user; } @override protected UserDTO doBackward(User User) {throw new AssertionError(" Reverse transform methods are not supported!" ); }}Copy the code

Looking at the doBackward method, which throws an assertion exception directly, rather than a business exception, this code tells the caller of the code that the method is not for you to call, and if you call it, I “assert” that you called it incorrectly.

If you think the add user API I wrote above is perfect, you are not a good programmer. We should ensure that any data input into the method body is legal.

Why validate a lot of people tell me that if these apis are provided to the front end to make calls, the front end will validate them, why would you validate them?

The answer is like this, I never trust anyone call me API or method, such as front-end validation fails, or some people through some special channel (such as Charles caught), data into directly to my API, that I was still in the business logic of the normal processing, so it is possible to produce dirty data!

Keep in mind that “dirty data is fatal”. Even the smallest amount of dirty data can cause you to stay up all night!

From: Lrwin’s tech blog

lrwinx.github.io/2017/03/04

Welcome to follow my wechat public account “Code farming breakthrough”, share Python, Java, big data, machine learning, artificial intelligence and other technologies, pay attention to code farming technology improvement, career breakthrough, thinking transition, 200,000 + code farming growth charge first stop, accompany you have a dream to grow together