{"id":54961,"date":"2026-01-29T20:34:47","date_gmt":"2026-01-30T04:34:47","guid":{"rendered":"https:\/\/griddb.net\/?p=54961"},"modified":"2026-01-29T20:34:47","modified_gmt":"2026-01-30T04:34:47","slug":"building-a-volunteer-matching-system-for-health-events","status":"publish","type":"post","link":"https:\/\/www.griddb.net\/en\/blog\/building-a-volunteer-matching-system-for-health-events\/","title":{"rendered":"Building a Volunteer-Matching System for Health Events"},"content":{"rendered":"<p>Welcome! We&#8217;re about to build something useful, a <strong>volunteer-matching platform<\/strong> that connects skilled medical professionals with health organizations that need them. It&#8217;s the kind of system you&#8217;d see powering real health events, from blood drives to vaccination clinics. By the time we&#8217;re done, you&#8217;ll understand how to architect and deploy a complete full-stack application that handles real-world complexity, including matching qualified people to opportunities, managing permissions across different user roles, and keeping everything secure.<\/p>\n<h2>The Stack: Technologies That Work Together<\/h2>\n<p>We&#8217;re using a carefully selected tech stack that mirrors what you&#8217;ll find in production environments:<\/p>\n<ul>\n<li><strong>Spring Boot &amp; Thymeleaf<\/strong> handles the business rules, data orchestration, and renders dynamic HTML templates on the server side.<\/li>\n<li><strong>GridDB<\/strong> (cloud-hosted NoSQL datastore) stores volunteer profiles, opportunities, and applications.<\/li>\n<\/ul>\n<p>Each technology serves a specific purpose, and together they create a seamless user experience backed by robust backend logic.<\/p>\n<h2>Learning Roadmap<\/h2>\n<p>We&#8217;ll move from foundation to mastery:<\/p>\n<ul>\n<li><strong>Setup &amp; Architecture<\/strong>: We&#8217;ll start by understanding the three-layer system design, laying out your Maven project structure, and configuring Spring Boot for success.<\/p>\n<\/li>\n<li><strong>Core Features<\/strong>: Next, we&#8217;ll implement the data model (entities, relationships, indexing) and set up GridDB integration.<\/p>\n<\/li>\n<li>\n<p><strong>User Interface &amp; Experience<\/strong>: Then we&#8217;ll create server-rendered Thymeleaf templates for browsing opportunities, applying for roles, and managing skills. You&#8217;ll see how server-side rendering keeps everything simple.<\/p>\n<\/li>\n<li>\n<p><strong>Security<\/strong>: We&#8217;ll add Spring Security authentication, implement role-based access control, ensuring organizers see different screens than volunteers, and ensuring data stays protected.<\/p>\n<\/li>\n<li>\n<p><strong>Real-World Patterns<\/strong>: Finally, integrate real-time slot updates.<\/p>\n<\/li>\n<\/ul>\n<p>By completing this tutorial, you&#8217;ll understand how to architect a full-stack Java application from database to user interface. More importantly, you&#8217;ll have a complete, deployable system you can adapt to other matching problems.<\/p>\n<p>Let&#8217;s build something real.<\/p>\n<h2>Project Setup<\/h2>\n<p>Here&#8217;s how we&#8217;ll set it up:<\/p>\n<ol>\n<li>Navigate to <a href=\"https:\/\/start.spring.io\/\">start.spring.io<\/a><\/li>\n<li>Configure your project:\n<ul>\n<li><strong>Project<\/strong>: Maven<\/li>\n<li><strong>Language<\/strong>: Java<\/li>\n<li><strong>Spring Boot<\/strong>: 3.5.x (latest stable version)<\/li>\n<li><strong>Group<\/strong>: com.example<\/li>\n<li><strong>Artifact<\/strong>: springboot-volunteermatching<\/li>\n<li><strong>Java Version<\/strong>: 21<\/li>\n<\/ul>\n<\/li>\n<li>Add the following dependencies:\n<ul>\n<li><strong>Spring Web<\/strong><\/li>\n<li><strong>Thymeleaf<\/strong><\/li>\n<li><strong>Spring Security<\/strong><\/li>\n<\/ul>\n<\/li>\n<li>Click <strong>Generate<\/strong> to download a ZIP file with our project structure\n<\/li>\n<\/ol>\n<p>Once you&#8217;ve downloaded and extracted the project, import it into your IDE.<\/p>\n<p>Next, we will create the package structure by grouping the classes based on their respective entities, e.g., a package <code>organization<\/code> contains the controller, service, DTO, etc.<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-sh\">volunteer-matching\/\n\u251c\u2500\u2500 pom.xml\n\u251c\u2500\u2500 src\/main\/java\/com\/volunteermatching\/\n\u2502   \u251c\u2500\u2500 config\/           (RestClient config)\n\u2502   \u251c\u2500\u2500 griddb\/\n\u2502   \u251c\u2500\u2500 griddbwebapi\/\n\u2502   \u251c\u2500\u2500 opportunity\/\n\u2502   \u251c\u2500\u2500 opportunity_requirement\/\n\u2502   \u251c\u2500\u2500 organization\/\n\u2502   \u251c\u2500\u2500 organization_member\/\n\u2502   \u251c\u2500\u2500 registration\/\n\u2502   \u2514\u2500\u2500 security\/         (Auth filters, RBAC)\n\u2502   \u251c\u2500\u2500 skill\/\n\u2502   \u251c\u2500\u2500 user\/\n\u2502   \u251c\u2500\u2500 volunteer_skill\/\n\u2514\u2500\u2500 src\/main\/resources\/\n    \u251c\u2500\u2500 templates\/        (Thymeleaf templates)\n    \u2514\u2500\u2500 application.properties   (Configuration)<\/code><\/pre>\n<\/div>\n<h2>Connecting to the GridDB Cloud<\/h2>\n<p>Configure the credentials for connecting to the GridDB Cloud through HTTP. Add the following to <code>application.properties<\/code>:<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-sh\"># GridDB Configuration\ngriddbcloud.base-url=https:\/\/cloud5197.griddb.com:443\/griddb\/v2\/gs_cluster\ngriddbcloud.auth-token=TTAxxxxxxx<\/code><\/pre>\n<\/div>\n<p>Next, create a <code>bean<\/code> of <code>org.springframework.web.client.RestClient<\/code> that provides a fluent, builder-based API for sending synchronous and asynchronous HTTP requests with cleaner syntax and improved readability.<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-java\">@Configuration\npublic class RestClientConfig {\n    final Logger LOGGER = LoggerFactory.getLogger(RestClientConfig.class);\n\n    @Bean(\"GridDbRestClient\")\n    public RestClient gridDbRestClient(\n            @NonNull @Value(\"${griddbcloud.base-url}\") final String baseUrl,\n            @NonNull @Value(\"${griddbcloud.auth-token}\") final String authToken) {\n        return RestClient.builder()\n                .baseUrl(baseUrl)\n                .defaultHeader(HttpHeaders.AUTHORIZATION, \"Basic \" + authToken)\n                .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)\n                .defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)\n                .defaultStatusHandler(\n                        status -> status.is4xxClientError() || status.is5xxServerError(), (request, response) -> {\n                            String responseBody = getResponseBody(response);\n                            LOGGER.error(\"GridDB API error: status={} body={}\", response.getStatusCode(), responseBody);\n                            if (response.getStatusCode().value() == 403) {\n                                LOGGER.error(\"Access forbidden - please check your auth token and permissions.\");\n                                throw new ForbiddenGridDbConnectionException(\"Access forbidden to GridDB Cloud API.\");\n                            }\n                            throw new GridDbException(\"GridDB API error: \", response.getStatusCode(), responseBody);\n                        })\n                .requestInterceptor((request, body, execution) -> {\n                    final long begin = System.currentTimeMillis();\n                    ClientHttpResponse response = execution.execute(request, body);\n                    logDuration(request, body, begin, response);\n                    return response;\n                })\n                .build();\n    }\n}<\/code><\/pre>\n<\/div>\n<ul>\n<li><code>@Bean(\"GridDbRestClient\")<\/code>: register this client as a Spring bean so we can inject it anywhere with <code>@Qualifier(\"GridDbRestClient\") final RestClient restClient<\/code>.<\/li>\n<li><code>.baseUrl(baseUrl)<\/code>: set the common base URL for all requests.<\/li>\n<li><code>.defaultHeader(...)<\/code>: adds a header that will be sent with every request.<\/li>\n<li><code>.defaultStatusHandler(...)<\/code>: when the API return an error (4xx or 5xx status code), log the error status. If the status is 403, throws a custom <code>ForbiddenGridDbConnectionException<\/code>. For any other error, it throws a general <code>GridDbException<\/code>.<\/li>\n<li><code>.requestInterceptor(<\/code>: log how long the request took for debugging performance.<\/li>\n<\/ul>\n<p>Next, create a helper that will be used by each service class to talk to the GridDB Cloud over the internet using HTTP requests. It wraps a pre-configured <code>RestClient<\/code> and provides easy-to-use methods for common database operations. All the complicated stuff (URLs, headers, error handling) is hidden inside this class.<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-java\">@Component\npublic class GridDbClient {\n    private final RestClient restClient;\n    public GridDbClient(@Qualifier(\"GridDbRestClient\") final RestClient restClient) {\n        this.restClient = restClient;\n    }\n\n    public void createContainer(final GridDbContainerDefinition containerDefinition) {\n        try {\n            restClient\n                    .post()\n                    .uri(\"\/containers\")\n                    .body(containerDefinition)\n                    .retrieve()\n                    .toBodilessEntity();\n        } catch (Exception e) {\n            throw new GridDbException(\"Failed to create container\", HttpStatusCode.valueOf(500), e.getMessage(), e);\n        }\n    }\n\n    public void registerRows(String containerName, Object body) {\n        try {\n            ResponseEntity<String> result = restClient\n                    .put()\n                    .uri(\"\/containers\/\" + containerName + \"\/rows\")\n                    .body(body)\n                    .retrieve()\n                    .toEntity(String.class);\n        } catch (Exception e) {\n            throw new GridDbException(\"Failed to execute PUT request\", HttpStatusCode.valueOf(500), e.getMessage(), e);\n        }\n    }\n\n    public AcquireRowsResponse acquireRows(String containerName, AcquireRowsRequest requestBody) {\n        try {\n            ResponseEntity<AcquireRowsResponse> responseEntity = restClient\n                    .post()\n                    .uri(\"\/containers\/\" + containerName + \"\/rows\")\n                    .body(requestBody)\n                    .retrieve()\n                    .toEntity(AcquireRowsResponse.class);\n            return responseEntity.getBody();\n        } catch (Exception e) {\n            throw new GridDbException(\"Failed to execute GET request\", HttpStatusCode.valueOf(500), e.getMessage(), e);\n        }\n    }\n\n    public SQLSelectResponse[] select(List<GridDbCloudSQLStmt> sqlStmts) {\n        try {\n            ResponseEntity<SQLSelectResponse[]> responseEntity = restClient\n                    .post()\n                    .uri(\"\/sql\/dml\/query\")\n                    .body(sqlStmts)\n                    .retrieve()\n                    .toEntity(SQLSelectResponse[].class);\n            return responseEntity.getBody();\n        } catch (Exception e) {\n            throw new GridDbException(\"Failed to execute \/sql\/dml\/query\", HttpStatusCode.valueOf(500), e.getMessage(), e);\n        }\n    }\n\n    public SqlExecutionResult[] executeSqlDDL(List<GridDbCloudSQLStmt> sqlStmts) {\n        try {\n            ResponseEntity<SqlExecutionResult[]> responseEntity =\n                    restClient.post().uri(\"\/sql\/ddl\").body(sqlStmts).retrieve().toEntity(SqlExecutionResult[].class);\n            return responseEntity.getBody();\n        } catch (Exception e) {\n            throw new GridDbException(\"Failed to execute SQL DDL\", HttpStatusCode.valueOf(500), e.getMessage(), e);\n        }\n    }\n\n    public SQLUpdateResponse[] executeSQLUpdate(List<GridDbCloudSQLStmt> sqlStmts) {\n        try {\n            ResponseEntity<SQLUpdateResponse[]> responseEntity = restClient\n                    .post()\n                    .uri(\"\/sql\/dml\/update\")\n                    .body(sqlStmts)\n                    .retrieve()\n                    .toEntity(SQLUpdateResponse[].class);\n            return responseEntity.getBody();\n        } catch (Exception e) {\n            throw new GridDbException(\"Failed to execute \/sql\/dml\/update\", HttpStatusCode.valueOf(500), e.getMessage(), e);\n        }\n    }\n}<\/code><\/pre>\n<\/div>\n<p>The constructor takes a <code>RestClient<\/code> that was named <code>GridDbRestClient<\/code>. The <code>@Qualifier<\/code> makes sure we get the correct one. Every method follows the same safe structure:<\/p>\n<ul>\n<li>Try to send an HTTP request using <code>restClient<\/code>.<\/li>\n<li>If something goes wrong (network issue, wrong data, server error), catch the exception.<\/li>\n<\/ul>\n<h2>Data Model using DTOs<\/h2>\n<p>Now, let&#8217;s create the Data Transfer Objects (DTOs). DTOs are simple classes that carry information from one part of the app to another, for example, from the database to the screen.<\/p>\n<p>In this project, the DTOs represent important things like users, skills, organizations, and volunteer events. Each DTO has its own fields to hold the data. Each DTO matches the structure of rows inside one GridDB container.<\/p>\n<ul>\n<li><code>UserDTO<\/code>: represents a user in the system, such as a volunteer or an organization admin. It&#8217;s used to create, update, or display user information.<\/li>\n<\/ul>\n<div class=\"clipboard\">\n<pre><code class=\"language-java\">public class UserDTO {\n        @Size(max = 255)\n        @UserIdValid\n        private String id;\n\n        @NotNull\n        @Size(max = 255)\n        @UserEmailUnique\n        private String email;\n\n        @NotNull\n        @Size(max = 255)\n        private String fullName;\n\n        @NotNull\n        private UserRole role;\n        \/\/ Setter and Getter\n    }<\/code><\/pre>\n<\/div>\n<ul>\n<li><code>SkillDTO<\/code>: represents a skill that volunteers can have, such as &#8220;First Aid&#8221; or &#8220;Paramedic.&#8221; It&#8217;s used to manage the list of available skills.<\/li>\n<\/ul>\n<div class=\"clipboard\">\n<pre><code class=\"language-java\">    public class SkillDTO {\n\n        @Size(max = 255)\n        private String id;\n\n        @NotNull\n        @Size(max = 255)\n        @SkillNameUnique\n        private String name;\n\n        public SkillDTO() {}\n\n        public SkillDTO(String id, String name) {\n            this.id = id;\n            this.name = name;\n        }\n        \/\/ Setter and Getter\n    }<\/code><\/pre>\n<\/div>\n<ul>\n<li><code>VolunteerSkillDTO<\/code>: links a user (volunteer) to a specific skill. It includes details like when the skill expires and its verification status. It&#8217;s useful for tracking what skills a volunteer has and their validity.\n<\/li>\n<li>\n<p><code>OrganizationDTO<\/code>: represents an organization that creates volunteer opportunities. It&#8217;s used to manage organization details.<\/p>\n<\/li>\n<li>\n<p><code>OrganizationMemberDTO<\/code>: links a user to an organization, specifying their role within it (e.g., member or admin). It&#8217;s used to manage who belongs to which organization.<\/p>\n<\/li>\n<li>\n<p><code>OpportunityDTO<\/code>: represents a volunteer opportunity, like an event that needs volunteers. It&#8217;s used to create and display opportunities.<\/p>\n<\/li>\n<li>\n<p><code>OpportunityRequirementDTO<\/code>: specifies the skills required for a volunteer opportunity. It links an opportunity to skills and indicates if a skill is mandatory.<\/p>\n<\/li>\n<li>\n<p><code>RegistrationDTO<\/code>: represents a volunteer&#8217;s registration for an opportunity. It tracks who signed up and the status of their registration.<\/p>\n<\/li>\n<\/ul>\n<h2>Service Layer and Business Logic<\/h2>\n<p>Next, we implement the service layer. The services will utilize these DTOs to handle business logic, communicate with GridDB Cloud through our client, and prepare data for the controllers.<\/p>\n<p>The Service class does not use any repository layer like JPA. Instead, it directly connects to GridDB, which is a database, using a <code>GridDbClient<\/code>. The Service class implements the interface, which means it must provide methods like <code>findAll()<\/code> to get all rows and <code>get()<\/code> to find one by ID, <code>create<\/code> to add a new row, and others. When fetching data, it sends requests to GridDB to get rows, then maps those rows into DTO objects. For saving or updating, it builds a string in JSON format with data and sends it to GridDB Cloud. It also generates unique IDs using <code>TsidCreator<\/code> and handles date times carefully by parsing and formatting them.<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-java\">@Service\npublic class RegistrationGridDBService implements RegistrationService {\n    private final Logger log = LoggerFactory.getLogger(getClass());\n    private final GridDbClient gridDbClient;\n    private final String TBL_NAME = \"VoMaRegistrations\";\n    public RegistrationGridDBService(final GridDbClient gridDbClient) {\n        this.gridDbClient = gridDbClient;\n    }\n\n    public void createTable() {\n        List&lt;GridDbColumn&gt; columns = List.of(\n                new GridDbColumn(\"id\", \"STRING\", Set.of(\"TREE\")),\n                new GridDbColumn(\"userId\", \"STRING\", Set.of(\"TREE\")),\n                new GridDbColumn(\"opportunityId\", \"STRING\", Set.of(\"TREE\")),\n                new GridDbColumn(\"status\", \"STRING\"),\n                new GridDbColumn(\"registrationTime\", \"TIMESTAMP\"));\n\n        GridDbContainerDefinition containerDefinition = GridDbContainerDefinition.build(TBL_NAME, columns);\n        this.gridDbClient.createContainer(containerDefinition);\n    }\n\n    @Override\n    public List&lt;RegistrationDTO&gt; findAll() {\n        AcquireRowsRequest requestBody =\n                AcquireRowsRequest.builder().limit(50L).sort(\"id ASC\").build();\n        AcquireRowsResponse response = this.gridDbClient.acquireRows(TBL_NAME, requestBody);\n        if (response == null || response.getRows() == null) {\n            log.error(\"Failed to acquire rows from GridDB\");\n            return List.of();\n        }\n        return response.getRows().stream()\n                .map(row -> {\n                    return extractRowToDTO(row);\n                })\n                .collect(Collectors.toList());\n    }\n\n    private RegistrationDTO extractRowToDTO(List&lt;Object&gt; row) {\n        RegistrationDTO dto = new RegistrationDTO();\n        dto.setId((String) row.get(0));\n        dto.setUserId((String) row.get(1));\n        dto.setOpportunityId((String) row.get(2));\n        try {\n            dto.setStatus(RegistrationStatus.valueOf(row.get(3).toString()));\n        } catch (Exception e) {\n            dto.setStatus(null);\n        }\n        try {\n            dto.setRegistrationTime(DateTimeUtil.parseToLocalDateTime(row.get(4).toString()));\n        } catch (Exception e) {\n            dto.setRegistrationTime(null);\n        }\n        return dto;\n    }\n\n    @Override\n    public RegistrationDTO get(final String id) {\n        AcquireRowsRequest requestBody = AcquireRowsRequest.builder()\n                .limit(1L)\n                .condition(\"id == '\" + id + \"'\")\n                .build();\n        AcquireRowsResponse response = this.gridDbClient.acquireRows(TBL_NAME, requestBody);\n        if (response == null || response.getRows() == null) {\n            log.error(\"Failed to acquire rows from GridDB\");\n            throw new NotFoundException(\"Registration not found with id: \" + id);\n        }\n        return response.getRows().stream()\n                .findFirst()\n                .map(row -> {\n                    return extractRowToDTO(row);\n                })\n                .orElseThrow(() -> new NotFoundException(\"Registration not found with id: \" + id));\n    }\n\n    public String nextId() {\n        return TsidCreator.getTsid().format(\"reg_%s\");\n    }\n\n    @Override\n    public String register(String userId, String opportunityId) {\n        RegistrationDTO registrationDTO = new RegistrationDTO();\n        registrationDTO.setUserId(userId);\n        registrationDTO.setOpportunityId(opportunityId);\n        registrationDTO.setStatus(RegistrationStatus.PENDING);\n        registrationDTO.setRegistrationTime(LocalDateTime.now());\n        return create(registrationDTO);\n    }\n}<\/code><\/pre>\n<\/div>\n<h3>Implement the validation<\/h3>\n<p>We create a dedicated Service class for validating volunteer registration requests against opportunity requirements. Some benefit from this approach:<\/p>\n<ul>\n<li>Hides the complexity. If the rules change later (e.g., &#8220;User needs 2 out of 3 skills&#8221;), we only change that one place<\/li>\n<li>Business validation logic isolated from HTTP concern<\/li>\n<li>Validation service can be used by REST APIs or other controllers<\/li>\n<li>Service can be unit tested independently<\/li>\n<li>Clear, focused exception handling with rich context<\/li>\n<\/ul>\n<div class=\"clipboard\">\n<pre><code class=\"language-java\">@Service\npublic class RegistrationValidationService {\n    private final Logger log = LoggerFactory.getLogger(getClass());\n    private final RegistrationService registrationService;\n    private final OpportunityService opportunityService;\n    private final OpportunityRequirementService opportunityRequirementService;\n    private final VolunteerSkillService volunteerSkillService;\n    private final SkillService skillService;\n\n    public RegistrationValidationService(\n            final RegistrationService registrationService,\n            final OpportunityService opportunityService,\n            final OpportunityRequirementService opportunityRequirementService,\n            final VolunteerSkillService volunteerSkillService,\n            final SkillService skillService) {\n        this.registrationService = registrationService;\n        this.opportunityService = opportunityService;\n        this.opportunityRequirementService = opportunityRequirementService;\n        this.volunteerSkillService = volunteerSkillService;\n        this.skillService = skillService;\n    }\n\n    public void validateRegistration(final String userId, final String opportunityId) {\n        \/\/ Check 1: User not already registered\n        validateNotAlreadyRegistered(userId, opportunityId);\n\n        \/\/ Check 2: Opportunity has available slots\n        validateSlotsAvailable(opportunityId);\n\n        \/\/ Check 3: User has mandatory skills\n        validateMandatorySkills(userId, opportunityId);\n    }\n\n    private void validateNotAlreadyRegistered(final String userId, final String opportunityId) {\n        Optional<RegistrationDTO> existingReg = registrationService.getByUserIdAndOpportunityId(userId, opportunityId);\n        if (existingReg.isPresent()) {\n            throw new AlreadyRegisteredException(userId, opportunityId);\n        }\n    }\n\n    private void validateSlotsAvailable(final String opportunityId) {\n        OpportunityDTO opportunity = opportunityService.get(opportunityId);\n        Long registeredCount = registrationService.countByOpportunityId(opportunityId);\n\n        if (registeredCount >= opportunity.getSlotsTotal()) {\n            throw new OpportunitySlotsFullException(opportunityId, opportunity.getSlotsTotal(), registeredCount);\n        }\n    }\n\n    private void validateMandatorySkills(final String userId, final String opportunityId) {\n        List<VolunteerSkillDTO> userSkills = volunteerSkillService.findAllByUserId(userId);\n        List<OpportunityRequirementDTO> opportunityRequirements =\n                opportunityRequirementService.findAllByOpportunityId(opportunityId);\n\n        for (OpportunityRequirementDTO requirement : opportunityRequirements) {\n            if (!requirement.getIsMandatory()) {\n                continue;\n            }\n\n            boolean hasSkill = userSkills.stream()\n                    .anyMatch(userSkill -> userSkill.getSkillId().equals(requirement.getSkillId()));\n\n            if (!hasSkill) {\n                SkillDTO skill = skillService.get(requirement.getSkillId());\n                String skillName = skill != null ? skill.getName() : \"Unknown Skill\";\n                throw new MissingMandatorySkillException(userId, opportunityId, requirement.getSkillId(), skillName);\n            }\n        }\n    }\n}<\/code><\/pre>\n<\/div>\n<p>This service depends on 5 collaborating services (Opportunity, OpportunityRequirement, VolunteerSkill, Skill, Registration) and throws custom exceptions for each validation failure, allowing callers to handle different error scenarios appropriately (e.g., different error messages, logging, etc).<\/p>\n<h2>HTTP Layer<\/h2>\n<p>Now, we need a class that handles all incoming web requests, processes user input, and sends back responses. It&#8217;s the bridge between the user&#8217;s browser and the application&#8217;s code logic.<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-java\">@Controller\n@RequestMapping(\"\/opportunities\")\npublic class OpportunityController {\n    private final Logger log = LoggerFactory.getLogger(getClass());\n    private final OpportunityService opportunityService;\n    private final RegistrationService registrationService;\n    private final RegistrationValidationService registrationValidationService;\n    private final UserService userService;\n    private final OpportunityRequirementService opportunityRequirementService;\n    private final SkillService skillService;\n\n    public OpportunityController(\n            final OpportunityService opportunityService,\n            final RegistrationService registrationService,\n            final RegistrationValidationService registrationValidationService,\n            final UserService userService,\n            final OpportunityRequirementService opportunityRequirementService,\n            final SkillService skillService) {\n        this.opportunityService = opportunityService;\n        this.registrationService = registrationService;\n        this.registrationValidationService = registrationValidationService;\n        this.userService = userService;\n        this.opportunityRequirementService = opportunityRequirementService;\n        this.skillService = skillService;\n    }\n\n    @GetMapping\n    public String list(final Model model, @AuthenticationPrincipal final CustomUserDetails userDetails) {\n        List<OpportunityDTO> allOpportunities = new ArrayList<>();\n        UserDTO user = userDetails != null\n                ? userService.getOneByEmail(userDetails.getUsername()).orElse(null)\n                : null;\n        if (userDetails != null\n                && userDetails.getOrganizations() != null\n                && !userDetails.getOrganizations().isEmpty()) {\n            OrganizationDTO org = userDetails.getOrganizations().get(0);\n            model.addAttribute(\"organization\", org);\n            allOpportunities = opportunityService.findAllByOrgId(org.getId());\n        } else {\n            model.addAttribute(\"organization\", null);\n            allOpportunities = opportunityService.findAll();\n        }\n        List<OpportunityResponse> opportunities = extractOpportunities(allOpportunities, user);\n        model.addAttribute(\"opportunities\", opportunities);\n        return \"opportunity\/list\";\n    }\n\n    @GetMapping(\"\/add\")\n    @PreAuthorize(SecurityExpressions.ORGANIZER_ONLY)\n    public String add(\n            @ModelAttribute(\"opportunity\") final OpportunityDTO opportunityDTO,\n            final Model model,\n            @AuthenticationPrincipal final CustomUserDetails userDetails) {\n        if (userDetails != null\n                && userDetails.getOrganizations() != null\n                && !userDetails.getOrganizations().isEmpty()) {\n            OrganizationDTO org = userDetails.getOrganizations().get(0);\n            opportunityDTO.setOrgId(org.getId());\n        }\n        opportunityDTO.setId(opportunityService.nextId());\n        return \"opportunity\/add\";\n    }\n\n    @PostMapping(\"\/add\")\n    @PreAuthorize(SecurityExpressions.ORGANIZER_ONLY)\n    public String add(\n            @ModelAttribute(\"opportunity\") @Valid final OpportunityDTO opportunityDTO,\n            final BindingResult bindingResult,\n            final RedirectAttributes redirectAttributes) {\n        if (bindingResult.hasErrors()) {\n            return \"opportunity\/add\";\n        }\n        opportunityService.create(opportunityDTO);\n        redirectAttributes.addFlashAttribute(WebUtils.MSG_SUCCESS, WebUtils.getMessage(\"opportunity.create.success\"));\n        return \"redirect:\/opportunities\";\n    }\n\n    @GetMapping(\"\/edit\/{id}\")\n    @PreAuthorize(SecurityExpressions.ORGANIZER_ONLY)\n    public String edit(@PathVariable(name = \"id\") final String id, final Model model) {\n        model.addAttribute(\"opportunity\", opportunityService.get(id));\n        return \"opportunity\/edit\";\n    }\n\n    @PostMapping(\"\/edit\/{id}\")\n    @PreAuthorize(SecurityExpressions.ORGANIZER_ONLY)\n    public String edit(\n            @PathVariable(name = \"id\") final String id,\n            @ModelAttribute(\"opportunity\") @Valid final OpportunityDTO opportunityDTO,\n            final BindingResult bindingResult,\n            final RedirectAttributes redirectAttributes) {\n        if (bindingResult.hasErrors()) {\n            return \"opportunity\/edit\";\n        }\n        opportunityService.update(id, opportunityDTO);\n        redirectAttributes.addFlashAttribute(WebUtils.MSG_SUCCESS, WebUtils.getMessage(\"opportunity.update.success\"));\n        return \"redirect:\/opportunities\";\n    }\n\n    @PostMapping(\"\/{id}\/registrations\")\n    public String registrations(\n            @PathVariable(name = \"id\") final String opportunityId,\n            final RedirectAttributes redirectAttributes,\n            @AuthenticationPrincipal final UserDetails userDetails) {\n        UserDTO user = userService\n                .getOneByEmail(userDetails.getUsername())\n                .orElseThrow(() -> new UsernameNotFoundException(\"User not found\"));\n        try {\n            \/\/ Validate registration using the validation service\n            registrationValidationService.validateRegistration(user.getId(), opportunityId);\n\n            \/\/ If validation passes, proceed with registration\n            OpportunityDTO opportunityDTO = opportunityService.get(opportunityId);\n            registrationService.register(user.getId(), opportunityId);\n            log.debug(\n                    \"Registration Successful - user: {}, opportunity: {}\",\n                    user.getFullName(),\n                    opportunityDTO.getTitle());\n            redirectAttributes.addFlashAttribute(\n                    WebUtils.MSG_INFO, WebUtils.getMessage(\"opportunity.registrations.success\"));\n            return \"redirect:\/opportunities\/\" + opportunityId;\n\n        } catch (AlreadyRegisteredException e) {\n            redirectAttributes.addFlashAttribute(\n                    WebUtils.MSG_ERROR, WebUtils.getMessage(\"opportunity.registrations.already_registered\"));\n            return \"redirect:\/opportunities\/\" + opportunityId;\n\n        } catch (OpportunitySlotsFullException e) {\n            redirectAttributes.addFlashAttribute(\n                    WebUtils.MSG_ERROR, WebUtils.getMessage(\"opportunity.registrations.full\"));\n            return \"redirect:\/opportunities\/\" + opportunityId;\n\n        } catch (MissingMandatorySkillException e) {\n            redirectAttributes.addFlashAttribute(\n                    WebUtils.MSG_ERROR,\n                    WebUtils.getMessage(\"opportunity.registrations.missing_skill\", e.getSkillName()));\n            return \"redirect:\/opportunities\/\" + opportunityId;\n        }\n    }\n}<\/code><\/pre>\n<\/div>\n<p>The <code>OpportunityController.java<\/code>:<\/p>\n<ul>\n<li>Doesn&#8217;t do the work itself; it delegates to specialized services. This keeps code organized and reusable.<\/li>\n<li>Manages everything related to <code>\/opportunities<\/code> URLs, for example, listing volunteer opportunities.<\/li>\n<li>Receives <strong>services<\/strong> it needs using <strong>constructor injection<\/strong>.<\/li>\n<li><code>@PreAuthorize<\/code> ensures only authorized users perform actions<\/li>\n<li>Validate registration using <code>registrationValidationService<\/code>. If validation fails, catches specific exceptions and shows error messages.<\/li>\n<li>Clean Controller: focus on orchestration only<\/li>\n<\/ul>\n<h2>User Interface Preview<\/h2>\n<ul>\n<li>Listing opportunity page:\n<\/li>\n<li>\n<p>Register page:<\/p>\n<\/li>\n<\/ul>\n<p><a href=\"\/wp-content\/uploads\/2026\/01\/volmatch_list_opportunity.png\"><img fetchpriority=\"high\" decoding=\"async\" src=\"\/wp-content\/uploads\/2026\/01\/volmatch_list_opportunity.png\" alt=\"\" width=\"1323\" height=\"456\" class=\"aligncenter size-full wp-image-54968\" srcset=\"\/wp-content\/uploads\/2026\/01\/volmatch_list_opportunity.png 1323w, \/wp-content\/uploads\/2026\/01\/volmatch_list_opportunity-300x103.png 300w, \/wp-content\/uploads\/2026\/01\/volmatch_list_opportunity-1024x353.png 1024w, \/wp-content\/uploads\/2026\/01\/volmatch_list_opportunity-768x265.png 768w, \/wp-content\/uploads\/2026\/01\/volmatch_list_opportunity-600x207.png 600w\" sizes=\"(max-width: 1323px) 100vw, 1323px\" \/><\/a><\/p>\n<p><a href=\"\/wp-content\/uploads\/2026\/01\/volmatch_register_opportunity.png\"><img decoding=\"async\" src=\"\/wp-content\/uploads\/2026\/01\/volmatch_register_opportunity.png\" alt=\"\" width=\"1343\" height=\"790\" class=\"aligncenter size-full wp-image-54969\" srcset=\"\/wp-content\/uploads\/2026\/01\/volmatch_register_opportunity.png 1343w, \/wp-content\/uploads\/2026\/01\/volmatch_register_opportunity-300x176.png 300w, \/wp-content\/uploads\/2026\/01\/volmatch_register_opportunity-1024x602.png 1024w, \/wp-content\/uploads\/2026\/01\/volmatch_register_opportunity-768x452.png 768w, \/wp-content\/uploads\/2026\/01\/volmatch_register_opportunity-600x353.png 600w\" sizes=\"(max-width: 1343px) 100vw, 1343px\" \/><\/a><\/p>\n<h2>Conclusion<\/h2>\n<p>Building a volunteer-matching web for a health event is a practical project that trains core skills: Spring Boot service design, server-rendered Thymeleaf UI, Cloud NoSQL integration, and RBAC. Feel free to add more feature like email notifications or calendar integration. Keep building, keep learning.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Welcome! We&#8217;re about to build something useful, a volunteer-matching platform that connects skilled medical professionals with health organizations that need them. It&#8217;s the kind of system you&#8217;d see powering real health events, from blood drives to vaccination clinics. By the time we&#8217;re done, you&#8217;ll understand how to architect and deploy a complete full-stack application that [&hellip;]<\/p>\n","protected":false},"author":41,"featured_media":54962,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[121],"tags":[],"class_list":["post-54961","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.1.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Building a Volunteer-Matching System for Health Events | GridDB: Open Source Time Series Database for IoT<\/title>\n<meta name=\"description\" content=\"Welcome! We&#039;re about to build something useful, a volunteer-matching platform that connects skilled medical professionals with health organizations that\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/griddb.net\/en\/blog\/building-a-volunteer-matching-system-for-health-events\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Building a Volunteer-Matching System for Health Events | GridDB: Open Source Time Series Database for IoT\" \/>\n<meta property=\"og:description\" content=\"Welcome! We&#039;re about to build something useful, a volunteer-matching platform that connects skilled medical professionals with health organizations that\" \/>\n<meta property=\"og:url\" content=\"https:\/\/griddb.net\/en\/blog\/building-a-volunteer-matching-system-for-health-events\/\" \/>\n<meta property=\"og:site_name\" content=\"GridDB: Open Source Time Series Database for IoT\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/griddbcommunity\/\" \/>\n<meta property=\"article:published_time\" content=\"2026-01-30T04:34:47+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.griddb.net\/wp-content\/uploads\/2026\/01\/cover_volunteermatchingplatform.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1376\" \/>\n\t<meta property=\"og:image:height\" content=\"768\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"griddb-admin\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@GridDBCommunity\" \/>\n<meta name=\"twitter:site\" content=\"@GridDBCommunity\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"griddb-admin\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"6 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/griddb.net\/en\/blog\/building-a-volunteer-matching-system-for-health-events\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/griddb.net\/en\/blog\/building-a-volunteer-matching-system-for-health-events\/\"},\"author\":{\"name\":\"griddb-admin\",\"@id\":\"https:\/\/www.griddb.net\/en\/#\/schema\/person\/4fe914ca9576878e82f5e8dd3ba52233\"},\"headline\":\"Building a Volunteer-Matching System for Health Events\",\"datePublished\":\"2026-01-30T04:34:47+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/griddb.net\/en\/blog\/building-a-volunteer-matching-system-for-health-events\/\"},\"wordCount\":1251,\"commentCount\":1,\"publisher\":{\"@id\":\"https:\/\/www.griddb.net\/en\/#organization\"},\"image\":{\"@id\":\"https:\/\/griddb.net\/en\/blog\/building-a-volunteer-matching-system-for-health-events\/#primaryimage\"},\"thumbnailUrl\":\"\/wp-content\/uploads\/2026\/01\/cover_volunteermatchingplatform.png\",\"articleSection\":[\"Blog\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/griddb.net\/en\/blog\/building-a-volunteer-matching-system-for-health-events\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/griddb.net\/en\/blog\/building-a-volunteer-matching-system-for-health-events\/\",\"url\":\"https:\/\/griddb.net\/en\/blog\/building-a-volunteer-matching-system-for-health-events\/\",\"name\":\"Building a Volunteer-Matching System for Health Events | GridDB: Open Source Time Series Database for IoT\",\"isPartOf\":{\"@id\":\"https:\/\/www.griddb.net\/en\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/griddb.net\/en\/blog\/building-a-volunteer-matching-system-for-health-events\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/griddb.net\/en\/blog\/building-a-volunteer-matching-system-for-health-events\/#primaryimage\"},\"thumbnailUrl\":\"\/wp-content\/uploads\/2026\/01\/cover_volunteermatchingplatform.png\",\"datePublished\":\"2026-01-30T04:34:47+00:00\",\"description\":\"Welcome! We're about to build something useful, a volunteer-matching platform that connects skilled medical professionals with health organizations that\",\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/griddb.net\/en\/blog\/building-a-volunteer-matching-system-for-health-events\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/griddb.net\/en\/blog\/building-a-volunteer-matching-system-for-health-events\/#primaryimage\",\"url\":\"\/wp-content\/uploads\/2026\/01\/cover_volunteermatchingplatform.png\",\"contentUrl\":\"\/wp-content\/uploads\/2026\/01\/cover_volunteermatchingplatform.png\",\"width\":1376,\"height\":768},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.griddb.net\/en\/#website\",\"url\":\"https:\/\/www.griddb.net\/en\/\",\"name\":\"GridDB: Open Source Time Series Database for IoT\",\"description\":\"GridDB is an open source time-series database with the performance of NoSQL and convenience of SQL\",\"publisher\":{\"@id\":\"https:\/\/www.griddb.net\/en\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.griddb.net\/en\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/www.griddb.net\/en\/#organization\",\"name\":\"Fixstars\",\"url\":\"https:\/\/www.griddb.net\/en\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.griddb.net\/en\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/griddb.net\/wp-content\/uploads\/2019\/04\/fixstars_logo_web_tagline.png\",\"contentUrl\":\"https:\/\/griddb.net\/wp-content\/uploads\/2019\/04\/fixstars_logo_web_tagline.png\",\"width\":200,\"height\":83,\"caption\":\"Fixstars\"},\"image\":{\"@id\":\"https:\/\/www.griddb.net\/en\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/www.facebook.com\/griddbcommunity\/\",\"https:\/\/x.com\/GridDBCommunity\",\"https:\/\/www.linkedin.com\/company\/griddb-by-toshiba\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.griddb.net\/en\/#\/schema\/person\/4fe914ca9576878e82f5e8dd3ba52233\",\"name\":\"griddb-admin\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.griddb.net\/en\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/5bceca1cafc06886a7ba873e2f0a28011a1176c4dea59709f735b63ae30d0342?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/5bceca1cafc06886a7ba873e2f0a28011a1176c4dea59709f735b63ae30d0342?s=96&d=mm&r=g\",\"caption\":\"griddb-admin\"},\"url\":\"https:\/\/www.griddb.net\/en\/author\/griddb-admin\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Building a Volunteer-Matching System for Health Events | GridDB: Open Source Time Series Database for IoT","description":"Welcome! We're about to build something useful, a volunteer-matching platform that connects skilled medical professionals with health organizations that","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/griddb.net\/en\/blog\/building-a-volunteer-matching-system-for-health-events\/","og_locale":"en_US","og_type":"article","og_title":"Building a Volunteer-Matching System for Health Events | GridDB: Open Source Time Series Database for IoT","og_description":"Welcome! We're about to build something useful, a volunteer-matching platform that connects skilled medical professionals with health organizations that","og_url":"https:\/\/griddb.net\/en\/blog\/building-a-volunteer-matching-system-for-health-events\/","og_site_name":"GridDB: Open Source Time Series Database for IoT","article_publisher":"https:\/\/www.facebook.com\/griddbcommunity\/","article_published_time":"2026-01-30T04:34:47+00:00","og_image":[{"width":1376,"height":768,"url":"https:\/\/www.griddb.net\/wp-content\/uploads\/2026\/01\/cover_volunteermatchingplatform.png","type":"image\/png"}],"author":"griddb-admin","twitter_card":"summary_large_image","twitter_creator":"@GridDBCommunity","twitter_site":"@GridDBCommunity","twitter_misc":{"Written by":"griddb-admin","Est. reading time":"6 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/griddb.net\/en\/blog\/building-a-volunteer-matching-system-for-health-events\/#article","isPartOf":{"@id":"https:\/\/griddb.net\/en\/blog\/building-a-volunteer-matching-system-for-health-events\/"},"author":{"name":"griddb-admin","@id":"https:\/\/www.griddb.net\/en\/#\/schema\/person\/4fe914ca9576878e82f5e8dd3ba52233"},"headline":"Building a Volunteer-Matching System for Health Events","datePublished":"2026-01-30T04:34:47+00:00","mainEntityOfPage":{"@id":"https:\/\/griddb.net\/en\/blog\/building-a-volunteer-matching-system-for-health-events\/"},"wordCount":1251,"commentCount":1,"publisher":{"@id":"https:\/\/www.griddb.net\/en\/#organization"},"image":{"@id":"https:\/\/griddb.net\/en\/blog\/building-a-volunteer-matching-system-for-health-events\/#primaryimage"},"thumbnailUrl":"\/wp-content\/uploads\/2026\/01\/cover_volunteermatchingplatform.png","articleSection":["Blog"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/griddb.net\/en\/blog\/building-a-volunteer-matching-system-for-health-events\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/griddb.net\/en\/blog\/building-a-volunteer-matching-system-for-health-events\/","url":"https:\/\/griddb.net\/en\/blog\/building-a-volunteer-matching-system-for-health-events\/","name":"Building a Volunteer-Matching System for Health Events | GridDB: Open Source Time Series Database for IoT","isPartOf":{"@id":"https:\/\/www.griddb.net\/en\/#website"},"primaryImageOfPage":{"@id":"https:\/\/griddb.net\/en\/blog\/building-a-volunteer-matching-system-for-health-events\/#primaryimage"},"image":{"@id":"https:\/\/griddb.net\/en\/blog\/building-a-volunteer-matching-system-for-health-events\/#primaryimage"},"thumbnailUrl":"\/wp-content\/uploads\/2026\/01\/cover_volunteermatchingplatform.png","datePublished":"2026-01-30T04:34:47+00:00","description":"Welcome! We're about to build something useful, a volunteer-matching platform that connects skilled medical professionals with health organizations that","inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/griddb.net\/en\/blog\/building-a-volunteer-matching-system-for-health-events\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/griddb.net\/en\/blog\/building-a-volunteer-matching-system-for-health-events\/#primaryimage","url":"\/wp-content\/uploads\/2026\/01\/cover_volunteermatchingplatform.png","contentUrl":"\/wp-content\/uploads\/2026\/01\/cover_volunteermatchingplatform.png","width":1376,"height":768},{"@type":"WebSite","@id":"https:\/\/www.griddb.net\/en\/#website","url":"https:\/\/www.griddb.net\/en\/","name":"GridDB: Open Source Time Series Database for IoT","description":"GridDB is an open source time-series database with the performance of NoSQL and convenience of SQL","publisher":{"@id":"https:\/\/www.griddb.net\/en\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.griddb.net\/en\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/www.griddb.net\/en\/#organization","name":"Fixstars","url":"https:\/\/www.griddb.net\/en\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.griddb.net\/en\/#\/schema\/logo\/image\/","url":"https:\/\/griddb.net\/wp-content\/uploads\/2019\/04\/fixstars_logo_web_tagline.png","contentUrl":"https:\/\/griddb.net\/wp-content\/uploads\/2019\/04\/fixstars_logo_web_tagline.png","width":200,"height":83,"caption":"Fixstars"},"image":{"@id":"https:\/\/www.griddb.net\/en\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/griddbcommunity\/","https:\/\/x.com\/GridDBCommunity","https:\/\/www.linkedin.com\/company\/griddb-by-toshiba"]},{"@type":"Person","@id":"https:\/\/www.griddb.net\/en\/#\/schema\/person\/4fe914ca9576878e82f5e8dd3ba52233","name":"griddb-admin","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.griddb.net\/en\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/5bceca1cafc06886a7ba873e2f0a28011a1176c4dea59709f735b63ae30d0342?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/5bceca1cafc06886a7ba873e2f0a28011a1176c4dea59709f735b63ae30d0342?s=96&d=mm&r=g","caption":"griddb-admin"},"url":"https:\/\/www.griddb.net\/en\/author\/griddb-admin\/"}]}},"_links":{"self":[{"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/posts\/54961","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/users\/41"}],"replies":[{"embeddable":true,"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/comments?post=54961"}],"version-history":[{"count":8,"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/posts\/54961\/revisions"}],"predecessor-version":[{"id":54972,"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/posts\/54961\/revisions\/54972"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/media\/54962"}],"wp:attachment":[{"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/media?parent=54961"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/categories?post=54961"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/tags?post=54961"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}