<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Durian Sosa</title>
    <description>The latest articles on DEV Community by Durian Sosa (@dmsosa).</description>
    <link>https://dev.to/dmsosa</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1261869%2F351f920e-8ff9-4e33-8f34-af41495d1812.png</url>
      <title>DEV Community: Durian Sosa</title>
      <link>https://dev.to/dmsosa</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dmsosa"/>
    <language>en</language>
    <item>
      <title>Microservices Project</title>
      <dc:creator>Durian Sosa</dc:creator>
      <pubDate>Tue, 09 Jul 2024 07:59:47 +0000</pubDate>
      <link>https://dev.to/dmsosa/microservices-project-4bcm</link>
      <guid>https://dev.to/dmsosa/microservices-project-4bcm</guid>
      <description>&lt;p&gt;⚙️Microservices project is inspired by piggymetrics by &lt;a class="mentioned-user" href="https://dev.to/sqshq"&gt;@sqshq&lt;/a&gt; "Alexander Lukyanchikov", but this implementation uses PostgreSQL and a simpler business logic, the main goal of this project is to show an example of microservices architecture.&lt;/p&gt;

&lt;p&gt;TechStack: PostgreSQL, Spring, Docker&lt;/p&gt;

&lt;p&gt;I'm thinking about what could be added to the project, currently working in providing a Swagger Documentation for it, but would like to hear your opinion.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxfpg9f1ihzpygg9g156s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxfpg9f1ihzpygg9g156s.png" alt="Image description" width="800" height="621"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I would like to know what errors there are in the project, what you think about it, and what could be added to make it more complete.&lt;/p&gt;

&lt;p&gt;I'm currently learning about message brokers like RabbitMQ and Kafka, I think that would be a good new feature for the project.&lt;/p&gt;

&lt;p&gt;You can find the repo here, simply clone it and you can run it in your local machine with an In-Memory H2 database.&lt;/p&gt;

&lt;p&gt;to continue without registration:&lt;br&gt;
username: demo&lt;br&gt;
password: password&lt;/p&gt;

</description>
      <category>springboot</category>
      <category>microservices</category>
      <category>java</category>
    </item>
    <item>
      <title>Microservices: Set Up a Gateway with UI (Thymeleaf)</title>
      <dc:creator>Durian Sosa</dc:creator>
      <pubDate>Mon, 01 Jul 2024 15:31:33 +0000</pubDate>
      <link>https://dev.to/dmsosa/microservices-set-up-a-gateway-with-ui-thymeleaf-1l12</link>
      <guid>https://dev.to/dmsosa/microservices-set-up-a-gateway-with-ui-thymeleaf-1l12</guid>
      <description>&lt;p&gt;Hello there, this is the structure of my microservices project with Spring;&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;1 Gateway (this is what I will cover in this chapter)&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
1 Authorization Server (OAuth2)&lt;br&gt;
3 REST APIs (Account Service, Statistics Service, Notification Service)&lt;br&gt;
1 Service Registry&lt;br&gt;
1 Config Server&lt;/p&gt;

&lt;p&gt;This is how it looks like&lt;/p&gt;

&lt;p&gt;The Gateway has a UI made with Thymeleaf, and the corresponding CSS and JavaScript &lt;em&gt;js code only has jQuery as external library&lt;/em&gt;. The purpose of jQuery is to make using JavaScript on your website much easier.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo0tz5zxgt66ofu85zwsm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo0tz5zxgt66ofu85zwsm.png" alt="Image description" width="800" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What our Gateway needs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The main role of a Gateway: Redirect requests to the correct Service, this is usually done with Spring Gateway but since we need to make an Authentication with the Authentication Server, this will be done with a custom WebClient instead (Please suggest any other solutions you have)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Take into account that it is the USER who needs to authenticate with the AUTHORIZATION SERVER.&lt;/p&gt;

&lt;p&gt;Dince Implicit Grant Flow is deprecated, I could not find a way for the Gateway to authenticate on the user's behalf, also I did not insist on it because it would go against the whole idea of the OAuth2 Code-Grant Flow (where it is always the Resource Owner a.k.a. the 'user' who authenticates and grant access to a third-party service, app or whatever it is). Please tell me if I'm wrong here.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Redirect the user to the Authentication Server, so the user grants access to the Gateway to his data in other services.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Send requests with the AccessToken to all the services (This is where WebClient comes in).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Good templates to show the HTML to the user&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Static files, such as CSS, JavaScript and images&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgdakilqn4c9b02rhvakt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgdakilqn4c9b02rhvakt.png" alt="These are stored in the Resource folder of the app" width="429" height="103"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Are you ready? Okay, let's go!&lt;/p&gt;

&lt;p&gt;Now the HTML page, the index.html shows how the final result should be when the data is available from the backend. For the actual code that contains Thymeleaf instructions, there are the fragments:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk400xx5fqmwmy3031ged.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk400xx5fqmwmy3031ged.png" alt="Image description" width="469" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, it may be different for your project, and I would like to focus on the general aspects of microservices:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The app must redirect the user to the AuthServer, we do this with the "ExceptionHandler" of our SecurityConfig, thanks to this every unauthenticated request is redirected to the Authorization Server via the authorization endpoint of the oauth2 flow&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;SecurityConfig:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    @Bean
    public SecurityWebFilterChain securityFilterChain(ServerHttpSecurity serverHttpSecurity) throws Exception {
        serverHttpSecurity
                .csrf(csrf -&amp;gt; csrf.disable())
                .cors(corsSpec -&amp;gt; corsSpec.configurationSource(corsConfigurationSource()))
                .oauth2Login(spec -&amp;gt; spec
                        .authenticationSuccessHandler(redirectSuccessHandler())
                        .authenticationFailureHandler(failureHandler())
                        .authorizationRequestResolver(customAuthorizationRequestResolver()))
                //redirect to Oauth2Server by default, oauth2server is in charge of login in and registering new users
                .exceptionHandling(exceptionHandlingSpec -&amp;gt; exceptionHandlingSpec.authenticationEntryPoint(new RedirectServerAuthenticationEntryPoint("/oauth2/authorization/gatewayClient")))
                .authorizeExchange( exchanges -&amp;gt;
                        exchanges
                        .pathMatchers("/css/**").permitAll()
                        .pathMatchers("/images/**").permitAll()
                        .pathMatchers("/js/**").permitAll()
                        .anyExchange().authenticated()
                 )
                .logout(logoutSpec -&amp;gt; logoutSpec
                        .logoutUrl("/logout")
                        .logoutSuccessHandler(oidcLogoutSuccessHandler()))
                .oauth2Client(Customizer.withDefaults())
                .oauth2ResourceServer(rs -&amp;gt; rs.jwt(jwt -&amp;gt; jwtConfigCustomizer()));
        return serverHttpSecurity.build();
    }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 2 and 3. Redirect the user's requests to the corresponding service.&lt;/p&gt;

&lt;p&gt;Supposing the user already granted access to the gateway after the Grant-Code Flow from OAuth2, we also need to attach the AccessToken as #a Bearer Token in the header of each request.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
#It is Load Balanced with Spring Discovery Client so to
#find any service we can use the 'application name' in the 
#URI.

@Configuration
public class WebClientConfig {

    public WebClientConfig() {

    }
    @Bean
    @LoadBalanced
    public WebClient.Builder webClientBuilder(ReactiveClientRegistrationRepository repository, ReactiveOAuth2AuthorizedClientService service) {
        AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager manager = new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(repository, service);
        ServerOAuth2AuthorizedClientExchangeFilterFunction oauthFilterFunction = new ServerOAuth2AuthorizedClientExchangeFilterFunction(manager);
        String clientId = "gatewayClient";
        oauthFilterFunction.setDefaultClientRegistrationId(clientId);
        return WebClient.builder().baseUrl("http://gateway:8061").filter(oauthFilterFunction);
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 4 and 5. Provide the Templates and Static Files&lt;/p&gt;

&lt;p&gt;Next comes the web configuration. Its purposes are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;To provide everything Thymeleaf needs to work (TemplateResolver, TemplateEngine, ViewResolver)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Indicate where MessageSources are found (this makes possible to serve the texts of our page in different languages)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Configure the endpoints that should return static resources (as CSS, JS and Images)&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;here you go:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Configuration
public class WebGlobalConfiguration implements ApplicationContextAware, WebFluxConfigurer {
    private ApplicationContext applicationContext;


    public WebGlobalConfiguration() {

    }
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    //Thymeleaf Config
    @Bean
    public SpringResourceTemplateResolver templateResolver() {
        SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
        templateResolver.setApplicationContext(applicationContext);
        templateResolver.setPrefix("classpath:/templates/");
        templateResolver.setSuffix(".html");
        return templateResolver;
    }    @Bean
    public ResourceBundleMessageSource messageSource() {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        return messageSource;

    }
    @Bean
    public SpringWebFluxTemplateEngine templateEngine() {
        SpringWebFluxTemplateEngine templateEngine = new SpringWebFluxTemplateEngine();
        templateEngine.setTemplateResolver(templateResolver());
        templateEngine.setMessageSource(messageSource());
        return templateEngine;
    }
    @Bean
    public ThymeleafReactiveViewResolver viewResolver() {
        ThymeleafReactiveViewResolver viewResolver = new ThymeleafReactiveViewResolver();
        viewResolver.setTemplateEngine(templateEngine());
        return viewResolver;
    }

    //Add Resourcer Handlers &amp;amp; View Resolvers
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/images/**").addResourceLocations("classpath:/static/images/");
        registry.addResourceHandler("/css/**").addResourceLocations("classpath:/static/css/");
        registry.addResourceHandler("/js/**").addResourceLocations("classpath:/static/js/");
    }

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.viewResolver(viewResolver());
    }


}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The WebController to process requests and provide the appropriate template as response :&lt;/p&gt;

&lt;p&gt;You will see some ENUM, this only applies to the Business Logic of my project, you can overlook them&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
@Controller
public class WebController {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    private AccountService accountService;
    private StatsService statsService;
    private ReactiveOAuth2AuthorizedClientService authorizedClientService;

    public WebController(AccountService accountService, StatsService statsService, ReactiveOAuth2AuthorizedClientService authorizedClientService) {
        this.accountService = accountService;
        this.statsService = statsService;
        this.authorizedClientService = authorizedClientService;
    }


    @ModelAttribute(name = "allAccountAvatar")
    public List&amp;lt;AccountAvatar&amp;gt; allAccountIcons() {
        return Arrays.asList(AccountAvatar.ALL);
    }
    @ModelAttribute(name = "allItemIcons")
    public List&amp;lt;ItemIcon&amp;gt; allItemIcons() {
        return Arrays.asList(ItemIcon.ALL);
    }
    @ModelAttribute(name = "allCategories")
    public List&amp;lt;Category&amp;gt; loadCategories() {
        return Arrays.asList(Category.ALL);
    }
    @ModelAttribute(name = "allCurrencies")
    public List&amp;lt;Currency&amp;gt; loadCurrencies() {
        return Arrays.asList(Currency.ALL);
    }
    @ModelAttribute(name = "allFrequencies")
    public List&amp;lt;Frequency&amp;gt; loadFrequencies() {
        return Arrays.asList(Frequency.ALL);
    }


    @RequestMapping(method = {RequestMethod.GET}, value = "/demo")
    public String showDemoPage(
            Model model
    ) {

        Mono&amp;lt;AccountDTO&amp;gt; accountDTO = accountService.getAccount("demo");
        model.addAttribute("account", accountDTO);
        model.addAttribute("logged", false);
        return "index";
    }
    @RequestMapping(method = {RequestMethod.GET}, value = "/index")
    public String showIndexPage(
            @RegisteredOAuth2AuthorizedClient(registrationId = "gatewayClient")
            OAuth2AuthorizedClient gatewayClient,
            Authentication authentication,
            Model model) {

        //Initializing account Mono, we define it later.
        // Because it is necessary to check if the account already exists

        //Getting username from the resource owner's authentication
        Mono&amp;lt;AccountDTO&amp;gt; accountMono;
        DefaultOidcUser user = (DefaultOidcUser) authentication.getPrincipal();
        String username = user.getName();

        //If account is null, then the gateway gets it or creates a new account

        if (model.getAttribute("account") == null) {
            accountMono = accountService.getAccount(username);
            model.addAttribute("account", accountMono);
            //Get stats of account or null if empty
            Flux&amp;lt;StatsDTO&amp;gt; statsDTOFlux = statsService.getStatsOfAccount(username);
            IReactiveDataDriverContextVariable statsVariable = new ReactiveDataDriverContextVariable(statsDTOFlux);

            model.addAttribute("stats", statsVariable);
            model.addAttribute("logged", false);
        }

        return "index";
    }


//This is a form to edit the account details such as accountName, Notes and Currency

    @RequestMapping(method = {RequestMethod.POST}, value = "/edit/{accountName}")
    public String editAccount(@PathVariable String accountName, @ModelAttribute(name = "account") AccountDTO account, BindingResult bindingResult, Model model) {
        Mono&amp;lt;AccountDTO&amp;gt; updatedAccount = accountService.editAccount(accountName, account);
        model.addAttribute("account", updatedAccount);
        model.addAttribute("logged", true);
        return "index";
    }
    @RequestMapping(method = {RequestMethod.POST}, value = "/save/{accountName}")
    public String saveAccountChanges(@PathVariable String accountName,
                                   @ModelAttribute(name = "account") AccountDTO account,
                                   BindingResult bindingResult,
                                   Model model) {



//This updates account's items and expenses

        Mono&amp;lt;AccountDTO&amp;gt; updatedAccount = accountService.editAccountItems(accountName, account).flatMap(
                accountDTO -&amp;gt; {
                    statsService.saveStatsOfAccount(accountDTO);
                    return Mono.just(accountDTO);
                }
        );

        Flux&amp;lt;StatsDTO&amp;gt; statsDTOFlux = statsService.getStatsOfAccount(accountName).delayElements(Duration.ofMillis(5000));

        IReactiveDataDriverContextVariable statsVariable = new ReactiveDataDriverContextVariable(statsDTOFlux);

        model.addAttribute("account", updatedAccount);
        model.addAttribute("stats", statsVariable);
        model.addAttribute("logged", true);

        return "index";
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The HTML, CSS and JavaScript is very personal so it may vary depending on your project, also I don't want to make the post extremely large, but if you want to take  a look at them I can leave them in the comments.&lt;/p&gt;

&lt;p&gt;The final result:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feebf1zr6b8bnakxlytyr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feebf1zr6b8bnakxlytyr.png" alt="Image description" width="800" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0hsuq7bprkhrd28r88is.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0hsuq7bprkhrd28r88is.png" alt="Image description" width="800" height="369"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3nykkist6rnwpunvpvae.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3nykkist6rnwpunvpvae.png" alt="Image description" width="800" height="373"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx6vzvwe4qkrsf1wspjr0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx6vzvwe4qkrsf1wspjr0.png" alt="Image description" width="800" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>thymeleaf</category>
      <category>microservices</category>
      <category>spring</category>
      <category>gateway</category>
    </item>
    <item>
      <title>Interesting, how to learn?</title>
      <dc:creator>Durian Sosa</dc:creator>
      <pubDate>Fri, 02 Feb 2024 07:54:39 +0000</pubDate>
      <link>https://dev.to/dmsosa/interesting-how-to-learn-elg</link>
      <guid>https://dev.to/dmsosa/interesting-how-to-learn-elg</guid>
      <description>&lt;p&gt;&lt;strong&gt;Trying to test if this can be edited later&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>learning</category>
      <category>codenewbie</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
