DZone
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
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Because the DevOps movement has redefined engineering responsibilities, SREs now have to become stewards of observability strategy.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Related

  • Design to Support New Query Parameters in GET Call Through Configurations Without Making Code Changes
  • Domain Logic Design Patterns in Java: Transaction Script, Domain Model, Table Module, and Service Layer
  • Building REST API Backend Easily With Ballerina Language
  • How To Implement and Design Twitter Search Backend Systems using Java Microservices?

Trending

  • Apple and Anthropic Partner on AI-Powered Vibe-Coding Tool – Public Release TBD
  • Efficient API Communication With Spring WebClient
  • How To Introduce a New API Quickly Using Quarkus and ChatGPT
  • Introducing Graph Concepts in Java With Eclipse JNoSQL
  1. DZone
  2. Data Engineering
  3. Data
  4. 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.

By 
Mohammed ZAHID user avatar
Mohammed ZAHID
DZone Core CORE ·
May. 24, 22 · Tutorial
Likes (2)
Comment
Save
Tweet
Share
10.6K 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.

Related

  • Design to Support New Query Parameters in GET Call Through Configurations Without Making Code Changes
  • Domain Logic Design Patterns in Java: Transaction Script, Domain Model, Table Module, and Service Layer
  • Building REST API Backend Easily With Ballerina Language
  • How To Implement and Design Twitter Search Backend Systems using Java Microservices?

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

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

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!