Skip to content

Commit 1fdf756

Browse files
committed
refactor: introduce USE_CATEGORY_MICROSERVICE feature with implementation that delegates calls to a service.
Part of #1459
1 parent 458fb0d commit 1fdf756

File tree

5 files changed

+351
-3
lines changed

5 files changed

+351
-3
lines changed
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/*
2+
* Copyright (C) 2009-2020 Slava Semushin <[email protected]>
3+
*
4+
* This program is free software; you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation; either version 2 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program; if not, write to the Free Software
16+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17+
*/
18+
package ru.mystamps.web.feature.category;
19+
20+
import org.slf4j.Logger;
21+
import org.slf4j.LoggerFactory;
22+
import org.springframework.boot.web.client.RestTemplateBuilder;
23+
import org.springframework.core.env.Environment;
24+
import org.springframework.http.ResponseEntity;
25+
import org.springframework.web.client.RestTemplate;
26+
import ru.mystamps.web.common.EntityWithParentDto;
27+
import ru.mystamps.web.common.LinkEntityDto;
28+
29+
import java.util.Date;
30+
import java.util.List;
31+
import java.util.Map;
32+
33+
/**
34+
* Implementation that delegates calls to a category service.
35+
*/
36+
@SuppressWarnings("PMD.TooManyMethods")
37+
public class ApiCategoryService implements CategoryService {
38+
39+
private static final Logger LOG = LoggerFactory.getLogger(ApiCategoryService.class);
40+
41+
private final RestTemplate restTemplate;
42+
43+
// Endpoints
44+
private final String countAllCategories;
45+
46+
public ApiCategoryService(RestTemplateBuilder restTemplateBuilder, Environment env) {
47+
String serviceHost = env.getRequiredProperty("service.category.host");
48+
49+
this.restTemplate = restTemplateBuilder
50+
.rootUri(serviceHost)
51+
.build();
52+
53+
this.countAllCategories = env.getRequiredProperty("service.category.count_all");
54+
}
55+
56+
@Override
57+
public String add(AddCategoryDto dto, Integer userId) {
58+
throw new UnsupportedOperationException();
59+
}
60+
61+
@Override
62+
public List<Integer> findIdsByNames(List<String> names) {
63+
throw new UnsupportedOperationException();
64+
}
65+
66+
@Override
67+
public List<Integer> findIdsWhenNameStartsWith(String name) {
68+
throw new UnsupportedOperationException();
69+
}
70+
71+
@Override
72+
public List<LinkEntityDto> findAllAsLinkEntities(String lang) {
73+
throw new UnsupportedOperationException();
74+
}
75+
76+
@Override
77+
public List<EntityWithParentDto> findCategoriesWithParents(String lang) {
78+
throw new UnsupportedOperationException();
79+
}
80+
81+
@Override
82+
public LinkEntityDto findOneAsLinkEntity(String slug, String lang) {
83+
throw new UnsupportedOperationException();
84+
}
85+
86+
@Override
87+
public long countAll() {
88+
LOG.debug("GET {}", countAllCategories);
89+
90+
ResponseEntity<Long> response = restTemplate.getForEntity(
91+
countAllCategories,
92+
Long.class
93+
);
94+
95+
Long result = response.getBody();
96+
97+
LOG.debug("Result: {} => {}", response.getStatusCodeValue(), result);
98+
99+
return result;
100+
}
101+
102+
@Override
103+
public long countCategoriesOf(Integer collectionId) {
104+
throw new UnsupportedOperationException();
105+
}
106+
107+
@Override
108+
public long countBySlug(String slug) {
109+
throw new UnsupportedOperationException();
110+
}
111+
112+
@Override
113+
public long countByName(String name) {
114+
throw new UnsupportedOperationException();
115+
}
116+
117+
@Override
118+
public long countByNameRu(String name) {
119+
throw new UnsupportedOperationException();
120+
}
121+
122+
@Override
123+
public long countAddedSince(Date date) {
124+
throw new UnsupportedOperationException();
125+
}
126+
127+
@Override
128+
public long countUntranslatedNamesSince(Date date) {
129+
throw new UnsupportedOperationException();
130+
}
131+
132+
@Override
133+
public Map<String, Integer> getStatisticsOf(Integer collectionId, String lang) {
134+
throw new UnsupportedOperationException();
135+
}
136+
137+
@Override
138+
public String suggestCategoryForUser(Integer userId) {
139+
throw new UnsupportedOperationException();
140+
}
141+
142+
}

src/main/java/ru/mystamps/web/feature/category/CategoryConfig.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@
1919

2020
import lombok.RequiredArgsConstructor;
2121
import org.slf4j.LoggerFactory;
22+
import org.springframework.boot.web.client.RestTemplateBuilder;
2223
import org.springframework.context.annotation.Bean;
2324
import org.springframework.context.annotation.Configuration;
25+
import org.springframework.core.env.Environment;
2426
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
2527

2628
/**
@@ -53,13 +55,18 @@ public SuggestionController suggestionCategoryController() {
5355
@RequiredArgsConstructor
5456
public static class Services {
5557

58+
private final Environment env;
5659
private final CategoryDao categoryDao;
60+
private final RestTemplateBuilder restTemplateBuilder;
5761

5862
@Bean
5963
public CategoryService categoryService() {
60-
return new CategoryServiceImpl(
61-
LoggerFactory.getLogger(CategoryServiceImpl.class),
62-
categoryDao
64+
return new TogglzWithFallbackCategoryService(
65+
new ApiCategoryService(restTemplateBuilder, env),
66+
new CategoryServiceImpl(
67+
LoggerFactory.getLogger(CategoryServiceImpl.class),
68+
categoryDao
69+
)
6370
);
6471
}
6572

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
/*
2+
* Copyright (C) 2009-2020 Slava Semushin <[email protected]>
3+
*
4+
* This program is free software; you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation; either version 2 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program; if not, write to the Free Software
16+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17+
*/
18+
package ru.mystamps.web.feature.category;
19+
20+
import lombok.RequiredArgsConstructor;
21+
import org.slf4j.Logger;
22+
import org.slf4j.LoggerFactory;
23+
import ru.mystamps.web.common.EntityWithParentDto;
24+
import ru.mystamps.web.common.LinkEntityDto;
25+
import ru.mystamps.web.support.togglz.Features;
26+
27+
import java.util.Date;
28+
import java.util.List;
29+
import java.util.Map;
30+
import java.util.concurrent.Callable;
31+
32+
/**
33+
* Implementation that delegates calls to a category service when the feature is enabled.
34+
*
35+
* When the {@code USE_CATEGORY_MICROSERVICE} feature is disabled or when a call has failed,
36+
* it falls back to the default implementation.
37+
*
38+
* @see ApiCategoryService
39+
* @see CategoryServiceImpl
40+
*/
41+
@RequiredArgsConstructor
42+
@SuppressWarnings("PMD.TooManyMethods")
43+
public class TogglzWithFallbackCategoryService implements CategoryService {
44+
45+
private static final Logger LOG =
46+
LoggerFactory.getLogger(TogglzWithFallbackCategoryService.class);
47+
48+
private final ApiCategoryService apiService;
49+
private final CategoryServiceImpl fallbackService;
50+
51+
@Override
52+
public String add(AddCategoryDto dto, Integer userId) {
53+
return executeOneOf(
54+
() -> apiService.add(dto, userId),
55+
() -> fallbackService.add(dto, userId)
56+
);
57+
}
58+
59+
@Override
60+
public List<Integer> findIdsByNames(List<String> names) {
61+
return executeOneOf(
62+
() -> apiService.findIdsByNames(names),
63+
() -> fallbackService.findIdsByNames(names)
64+
);
65+
}
66+
67+
@Override
68+
public List<Integer> findIdsWhenNameStartsWith(String name) {
69+
return executeOneOf(
70+
() -> apiService.findIdsWhenNameStartsWith(name),
71+
() -> fallbackService.findIdsWhenNameStartsWith(name)
72+
);
73+
}
74+
75+
@Override
76+
public List<LinkEntityDto> findAllAsLinkEntities(String lang) {
77+
return executeOneOf(
78+
() -> apiService.findAllAsLinkEntities(lang),
79+
() -> fallbackService.findAllAsLinkEntities(lang)
80+
);
81+
}
82+
83+
@Override
84+
public List<EntityWithParentDto> findCategoriesWithParents(String lang) {
85+
return executeOneOf(
86+
() -> apiService.findCategoriesWithParents(lang),
87+
() -> fallbackService.findCategoriesWithParents(lang)
88+
);
89+
}
90+
91+
@Override
92+
public LinkEntityDto findOneAsLinkEntity(String slug, String lang) {
93+
return executeOneOf(
94+
() -> apiService.findOneAsLinkEntity(slug, lang),
95+
() -> fallbackService.findOneAsLinkEntity(slug, lang)
96+
);
97+
}
98+
99+
@Override
100+
public long countAll() {
101+
return executeOneOf(
102+
apiService::countAll,
103+
fallbackService::countAll
104+
);
105+
}
106+
107+
@Override
108+
public long countCategoriesOf(Integer collectionId) {
109+
return executeOneOf(
110+
() -> apiService.countCategoriesOf(collectionId),
111+
() -> fallbackService.countCategoriesOf(collectionId)
112+
);
113+
}
114+
115+
@Override
116+
public long countBySlug(String slug) {
117+
return executeOneOf(
118+
() -> apiService.countBySlug(slug),
119+
() -> fallbackService.countBySlug(slug)
120+
);
121+
}
122+
123+
@Override
124+
public long countByName(String name) {
125+
return executeOneOf(
126+
() -> apiService.countByName(name),
127+
() -> fallbackService.countByName(name)
128+
);
129+
}
130+
131+
@Override
132+
public long countByNameRu(String name) {
133+
return executeOneOf(
134+
() -> apiService.countByNameRu(name),
135+
() -> fallbackService.countByNameRu(name)
136+
);
137+
}
138+
139+
@Override
140+
public long countAddedSince(Date date) {
141+
return executeOneOf(
142+
() -> apiService.countAddedSince(date),
143+
() -> fallbackService.countAddedSince(date)
144+
);
145+
}
146+
147+
@Override
148+
public long countUntranslatedNamesSince(Date date) {
149+
return executeOneOf(
150+
() -> apiService.countUntranslatedNamesSince(date),
151+
() -> fallbackService.countUntranslatedNamesSince(date)
152+
);
153+
}
154+
155+
@Override
156+
public Map<String, Integer> getStatisticsOf(Integer collectionId, String lang) {
157+
return executeOneOf(
158+
() -> apiService.getStatisticsOf(collectionId, lang),
159+
() -> fallbackService.getStatisticsOf(collectionId, lang)
160+
);
161+
}
162+
163+
@Override
164+
public String suggestCategoryForUser(Integer userId) {
165+
return executeOneOf(
166+
() -> apiService.suggestCategoryForUser(userId),
167+
() -> fallbackService.suggestCategoryForUser(userId)
168+
);
169+
}
170+
171+
private static <T> T executeOneOf(Callable<T> firstImpl, Callable<T> defaultImpl) {
172+
try {
173+
if (Features.USE_CATEGORY_MICROSERVICE.isActive()) {
174+
try {
175+
return firstImpl.call();
176+
} catch (UnsupportedOperationException ignored) {
177+
// the method isn't yet implemented, fallback to the default implementation
178+
179+
} catch (RuntimeException e) { // NOPMD: AvoidCatchingGenericException; catch-all
180+
LOG.warn(
181+
"Failed to call a category service. Fallback to default implementation",
182+
e
183+
);
184+
}
185+
}
186+
return defaultImpl.call();
187+
188+
} catch (Exception ex) { // NOPMD: AvoidCatchingGenericException; try to catch-all
189+
throw new RuntimeException(ex); // NOPMD: AvoidThrowingRawExceptionTypes
190+
}
191+
}
192+
193+
}

src/main/java/ru/mystamps/web/support/togglz/Features.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ public enum Features implements Feature {
2828
@EnabledByDefault
2929
SEARCH_IN_COLLECTION,
3030

31+
@Label("Use a category microservice for the category-related functions")
32+
USE_CATEGORY_MICROSERVICE,
33+
3134
@Label("Use a country microservice for the country-related functions")
3235
USE_COUNTRY_MICROSERVICE,
3336

src/main/resources/application.properties

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ app.mail.robot.email: [email protected]
4747
service.country.host: http://127.0.0.1:8081
4848
service.country.count_all: /v0.1/countries/count
4949

50+
service.category.host: http://127.0.0.1:8082
51+
service.category.count_all: /v0.1/categories/count
52+
5053
# A timeout for connecting and reading from a site (in milliseconds).
5154
# 1000ms = 1sec, that means that the max time for connecting will be 1 sec and
5255
# max time for reading the content will be also 1 sec. A timeout of zero is

0 commit comments

Comments
 (0)