DZone
Java Zone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
  • Refcardz
  • Trend Reports
  • Webinars
  • Zones
  • |
    • Agile
    • AI
    • Big Data
    • Cloud
    • Database
    • DevOps
    • Integration
    • IoT
    • Java
    • Microservices
    • Open Source
    • Performance
    • Security
    • Web Dev
DZone > Java Zone > How to Design a CRUD Web Service for Inheritable Entity

How to Design a CRUD Web Service for Inheritable Entity

This post will explain how to design a scalable web service that allows CRUD operations over an inheritable entity.

Mohammed ZAHID user avatar by
Mohammed ZAHID
CORE ·
May. 24, 22 · Java Zone · Tutorial
Like (2)
Save
Tweet
5.31K Views

Join the DZone community and get the full member experience.

Join For Free

Introduction

Sometimes we need to develop a web service that provides CRUD (Create, Read, Update, and Delete) operations for an entity that supports inheritance. However, the traditional layered architecture (controller layer, service layer, and repository layer) is not enough to have a clean and scalable code. Indeed, if we put the business logic and mapping code inside the service layer, we will have two main problems:

  • Having multiple conditions based on the DTO class type
  • Each time we want to introduce a new subclass, we need to modify the service layer, which is not good if we are developing a framework.

The code below illustrates what the service class will look like if we use the traditional architecture:

Java
 
public UppserClassServiceImpl implements UppserClassService{
  ...
  public UpperClassDto save(UpperClassDto upperClassDto){
      if(upperClassDto instanceof SubClass1Dto){
          SubClass1Dto dto= (SubClass1Dto) upperClassDto;
          // Mapping data from DTO to Entity
          SubClass1Entity entity= new SubClass1Entity();
          entity.setField1(dto.getFieldA());
          ...
          //Perform some business logic
          ...
          //Save entity
          repository.save(entity);
          dto.setId(entity.getId);
      }
      else if(upperClassDto instanceof SubClass2Dto){
          //make similar code
          ...
      }
      else if(...){
          ....
      }
      ...
      return upperClassDto;
  }
  ...
}


As we see, for one CRUD operation, we have many conditions and mapping code lines for each subclass. Therefore, we can imagine how complex the code will be for the whole service class.

Proposed Design

The following class diagram illustrates the proposed design:

  • EntityController: Rest controller that handles the incoming requests
  • EntityManager: Dispatches the requests received by the controller to the correct subentity service
  • EntityService: Generic interface that defines the CRUD operations for subentities
  • SubEntityNService: Specific implementation of CRUD operations for the subentity N; it contains the code that performs the subentity mapping and the business logic
  • Repository: Provides database access method

Class diagram

Implementation Example

In this part, we will see an implementation example of the proposed design based on the Java programming language with Spring Boot.

For this example, suppose that we want to provide a real estate management web service.

Annotation

This annotation is used to link the Entities and DTO to the corresponding service classes. 

Java
 
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface EntityHandler {
  /**
  * The name of the bean that provides CRUD operations for annotated entity or DTO  
  */
  String beanName();
}


Entities classes

The following classes represent the real estate database entities. We have an abstract class from which all of the real estate types are inheriting. For this example, we will use apartment, house and land as subclasses.

Java
 
@Data
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)//signle tableinheritance strategy
@DiscriminatorColumn(name = "type")
@Table(name = "reat_estate")
public abstract class RealEstate implements Serializable{
      
  	@Id
	private Integer id;
  
  	private Double area;
  
  	@Column(name="land_title")
  	private String landTitle;
  
  	...
}

@Data
@Entity
@DiscriminatorValue("apartment")
@EntityHandler("apartmentService")
public class Apartment extends RealEstate {
	...
}

@Data
@Entity
@DiscriminatorValue("house")
@EntityHandler("houseService")
public class House extends RealEstate {
    
    @Column(name="floor_count")
    private Integer floorCount;
    ...
}

@Data
@Entity
@DiscriminatorValue("land")
@EntityHandler("landService")
public class Land extends RealEstate {
    
    @Column(name="contains_well")
    private Boolean containsWell;
  
  	...
}


Data Transfer Objects Classes (DTO)

Here, we define the DTO classes that correspond to each entity. We use inheritance between DTO classes as below as we did with entities using Jackson annotations:

Java
 
@Data
@JsonTypeInfo(
  use = JsonTypeInfo.Id.NAME, 
  include = JsonTypeInfo.As.PROPERTY, 
  property = "type")
@JsonSubTypes({ 
  @Type(value = Apartment.class, name = "apartment"), 
  @Type(value = House.class, name = "house"),
  @Type(value = Land.class, name = "land")
})
public abstract class RealEstateDto implements Serializable{

	private Integer id;
  
  	private Double area;
  
  	private String landTitle;
  
  	...
}

@Data
@EntityHandler("apartmentService")
public class ApartmentDto extends RealEstateDto {
  
	...
}

@Data
@EntityHandler("houseService")
public class HouseDto extends RealEstateDto {
    
    private Integer floorCount;
  
    ...
}

@Data
@EntityHandler("landService")
public class LandDto extends RealEstateDto {
    
    private Boolean containsWell;
  
  	...
}


Service Layer

The service layer implements the CRUD operation for the subclasses. In the code below, we used an interface to define the CRUD methods signatures followed by an implementation example for the Apartment subclass.

Java
 
public interface RealEstateService<E extends RealEstate,D extends RealEstateDto> {

  /**
  * converts the entity given as parameter to the correspending STO
  */
  D get(E entity);
  
  /**
  * Saves DTO in database
  */
  D save(D dto);
  
  /**
  * Updates and entity from the corresponding DTO
  */
  D update(E entity, D dto);
  
  /**
  * Deletes entity from databse
  */
  void delete(E entity);
	
}

@Service("apartmentService")
@RequiredArgsConstructor(onConstructor = @_(@Autowired)) //use @__(@Autowired) for jdk 11
public ApartmentService implements RealEstateService<Apartment,ApartmentDto> {

  	protected final RealEstateRepository realEstateRepository;
  
  	@Override
	public ApartmentDto get(Apartment apartment){
    	ApartmentDto dto = new ApartmentDto();
      	dto.setId(apartment.getId());
      	...
        return dto;
    }

	@Override
  	public ApartmentDto save(ApartmentDto dto){
    	Apartment entity= new Apartment();
      	entity.setArea(dto.getArea());
		...
		// Business logic if exists
        realEstateRepository.save(entity);
        return get(entity);
    }
  
  	@Override
  	public ApartmentDto update(Apartment entity, ApartmentDto dto){
      	entity.setArea(dto.getArea());
		...
		// Business logic if exists
        realEstateRepository.save(entity);
        return get(entity);
    }
  
    @Override
	public void delete(Apartment entity){
      	// Business logic if exists
    	realEstateRepository.delete(entity);
    }
}


@Service("houseService")
@RequiredArgsConstructor(onConstructor = @_(@Autowired)) //use @__(@Autowired) for jdk 11
public HouseService implements RealEstateService<House,HouseDto> {
	//Use the samelogic as ApartmentService
  	...
}

@Service("landService")
@RequiredArgsConstructor(onConstructor = @_(@Autowired)) //use @__(@Autowired) for jdk 11
public LandService implements RealEstateService<Land,LandDto> {
	//Use the samelogic as ApartmentService
  	...
}


Manager Layer

The management layer has the responsibility of calling the right services for the incoming requests. To do so, we will extract the CrudHandler annotation from the Entity/DTO we have to get the associated bean name. Then we will get the bean from the Spring context in order to call the desired CRUD operation.

Java
 
@Service
@Transactional
@RequiredArgsConstructor(onConstructor = @_(@Autowired)) //use @__(@Autowired) for jdk 11
public class RealEstateManager {

	private final ApplicationContext appContext;
  
	private final RealEstateRepository realEstateRepository;
  
	public RealEstateDto get(Integer id){
    	Optional<RealEstate> opt = realEstateRepository.findById(id);
    	//manage Optional is empty
		RealEstate entity = opt.get();
  		CrudHandler annotation = entity.getAnnotation(CrudHandler.class);
    	//check annotation not null
    	RealEstateService service = appContext.getBean(annotation.beanName,RealEstateService.class);
    	//check serviceis not null
  		return service.get(entity);
	}
  
	public RealEstateDto save(RealEstateDto dto){
    	CrudHandler annotation = dto.getAnnotation(CrudHandler.class);
    	//check annotation not null
    	RealEstateService service = appContext.getBean(annotation.beanName,RealEstateService.class);
    	//check serviceis not null
    	return service.save(dto);
	}
  
	public RealEstateDto update(Integer id, RealEstateDto dto){
    	Optional<RealEstate> opt = realEstateRepository.findById(id);
    	//manage Optional is empty
		RealEstate entity = opt.get();
    	CrudHandler annotation = entity.getAnnotation(CrudHandler.class);
    	//check annotation not null
    	RealEstateService service = appContext.getBean(annotation.beanName,RealEstateService.class);
    	//check serviceis not null
    	return service.update(entity, dto);
	}
  
	public void delete(Integer id){
    	Optional<RealEstate> opt = realEstateRepository.findById(id);
    	//manage Optional is empty
		RealEstate entity = opt.get();
    	CrudHandler annotation = entity.getAnnotation(CrudHandler.class);
    	//check annotation not null
    	RealEstateService service = appContext.getBean(annotation.beanName,RealEstateService.class);
    	//check serviceis not null
    	return service.delete(entity);
	}

}


Conclusion

In this post, we have seen a new manner for designing a scalable CRUD web service. In order to support a new subclass, all we have to do is to define the corresponding entity, DTO, and service class without any modification in other layers.

Database Design Service layer Data Types Web Service

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • Major PostgreSQL Features You Should Know About
  • Anatomy of a Webhook HTTP Request
  • Remote Debugging and Developer Observability
  • Build a Data Pipeline on AWS With Kafka, Kafka Connect, and DynamoDB

Comments

Java Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • MVB Program
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends:

DZone.com is powered by 

AnswerHub logo