Skip to content
CMO & CTO
CMO & CTO

Closing the Bridge Between Marketing and Technology, By Luis Fernandez

  • Digital Experience
    • Experience Strategy
    • Experience-Driven Commerce
    • Multi-Channel Experience
    • Personalization & Targeting
    • SEO & Performance
    • User Journey & Behavior
  • Marketing Technologies
    • Analytics & Measurement
    • Content Management Systems
    • Customer Data Platforms
    • Digital Asset Management
    • Marketing Automation
    • MarTech Stack & Strategy
    • Technology Buying & ROI
  • Software Engineering
    • Software Engineering
    • Software Architecture
    • General Software
    • Development Practices
    • Productivity & Workflow
    • Code
    • Engineering Management
    • Business of Software
    • Code
    • Digital Transformation
    • Systems Thinking
    • Technical Implementation
  • About
CMO & CTO

Closing the Bridge Between Marketing and Technology, By Luis Fernandez

OSGi Services in Sling: Clean Boundaries

Posted on February 17, 2012 By Luis Fernandez

OSGi Services in Sling: Clean Boundaries. Apache Sling from a practitioner’s perspective with lessons that stick. If you are building content apps on top of Sling and Jackrabbit, keeping logic in tidy units pays off like compound interest.

Dialogue intro

Dev: My Sling servlet is a thousand lines. It touches the Repository, does JSON, sends email and returns HTML. It works, but I am scared to touch it.

Reviewer: Sounds like your servlet is a backpack full of bowling balls. Let the servlet be a thin HTTP edge. Move the guts into OSGi services and wire them with Declarative Services. Apache Sling loves that style.

Dev: Why not just new the classes I need inside the servlet?

Reviewer: Because new glues you to concrete types. OSGi service boundaries let you swap pieces, test them, and keep your HTTP layer lightweight.

Evidence

In Sling projects we keep seeing the same pattern. Teams that push logic into plain OSGi services ship faster and with fewer side effects. Some quick signs your boundaries need love:

  • Servlets that open sessions and pass them around everywhere
  • Static helpers that hide state and make tests flaky
  • Business rules tied to Resource API details
  • Hard coded config that is different on author and publish

Flip it. Create small service interfaces with clear inputs and outputs. Let DS find the right service at runtime. Configure with OSGi, not constants.

package com.example.content.api;

public interface ArticleService {
    Article read(String path);
    void publish(Article article);
}
package com.example.content.impl;

import com.example.content.api.ArticleService;
import org.apache.felix.scr.annotations.*;
import org.apache.sling.api.resource.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Component(label = "Article Service", metatype = true, immediate = true)
@Service(ArticleService.class)
@Properties({
    @Property(name = "service.description", value = "Reads and publishes articles"),
    @Property(name = "service.vendor", value = "Example Co")
})
public class ArticleServiceImpl implements ArticleService {

    private static final Logger log = LoggerFactory.getLogger(ArticleServiceImpl.class);

    @Reference
    private ResourceResolverFactory resolverFactory;

    @Property(label = "Publish root", value = "/content/published")
    public static final String PROP_PUBLISH_ROOT = "publish.root";

    private String publishRoot;

    @Activate
    protected void activate(org.osgi.service.cm.ConfigurationAdmin config, java.util.Map<String, Object> props) {
        this.publishRoot = (String) props.get(PROP_PUBLISH_ROOT);
    }

    @Override
    public Article read(String path) {
        try (ResourceResolver rr = resolverFactory.getAdministrativeResourceResolver(null)) {
            Resource res = rr.getResource(path);
            return Article.fromResource(res);
        } catch (Exception e) {
            log.error("Read failed for {}", path, e);
            throw new RuntimeException(e);
        }
    }

    @Override
    public void publish(Article article) {
        try (ResourceResolver rr = resolverFactory.getAdministrativeResourceResolver(null)) {
            rr.copy(article.getPath(), publishRoot + article.getPath());
            rr.commit();
        } catch (Exception e) {
            log.error("Publish failed for {}", article.getPath(), e);
            throw new RuntimeException(e);
        }
    }
}

Now your Sling servlet only calls ArticleService. You can mock it in tests, replace it per run mode, and keep HTTP neat.

Build notes

Use Felix SCR annotations for DS. Sling scans and registers them at startup. Keep these habits:

  • Program to interfaces. Export the API package, keep impl packages private in the bundle
  • Reference other services. Let DS inject with @Reference instead of ServiceTracker boilerplate
  • Push config to OSGi. Put cfg files under Sling run mode folders so author and publish can differ
# /apps/myapp/config.author/com.example.content.impl.ArticleServiceImpl.cfg
publish.root=/content/author-published

# /apps/myapp/config.publish/com.example.content.impl.ArticleServiceImpl.cfg
publish.root=/content/live
package com.example.content.servlets;

import com.example.content.api.ArticleService;
import org.apache.sling.api.servlets.*;
import org.apache.felix.scr.annotations.*;

@Component
@Service(Servlet.class)
@SlingServlet(paths = "/bin/article/publish", methods = "POST")
public class PublishServlet extends SlingAllMethodsServlet {

    @Reference
    private ArticleService articleService;

    @Override
    protected void doPost(SlingHttpServletRequest req, SlingHttpServletResponse resp)
            throws java.io.IOException {
        String path = req.getParameter("path");
        articleService.publish(articleService.read(path));
        resp.getWriter().write("ok");
    }
}

Risks

Service availability. DS can start services in any order. Mark references as optional only if you handle the missing case. For required refs, keep policy static so DS handles bind and unbind cleanly.

ResourceResolver leaks. Always close the resolver. Try with resources is your friend. If you still use administrative resolvers, guard them and plan a move to service users once your security model is ready.

Classloading surprises. Export only what you mean to share. Split API and impl into separate packages to avoid split package pain.

Config drift. Keep cfg files in version control under /apps so they ride your release train. Avoid tweaking by hand on a single node.

Graceful exit

Start with one messy servlet. Carve out a tiny interface and one service. Wire it with DS. Push one property into OSGi config. That is it. Repeat next sprint. With mobile traffic climbing and Facebook filing for an IPO this month, the web is not slowing down. Clean OSGi services in Sling give you small parts that ship fast, test well, and survive the next pivot.

Software Architecture Software Engineering Technical Implementation

Post navigation

Previous post
Next post
  • Digital Experience (94)
    • Experience Strategy (19)
    • Experience-Driven Commerce (5)
    • Multi-Channel Experience (9)
    • Personalization & Targeting (21)
    • SEO & Performance (10)
  • Marketing Technologies (92)
    • Analytics & Measurement (14)
    • Content Management Systems (45)
    • Customer Data Platforms (4)
    • Digital Asset Management (8)
    • Marketing Automation (6)
    • MarTech Stack & Strategy (10)
    • Technology Buying & ROI (3)
  • Software Engineering (310)
    • Business of Software (20)
    • Code (30)
    • Development Practices (52)
    • Digital Transformation (21)
    • Engineering Management (25)
    • General Software (82)
    • Productivity & Workflow (30)
    • Software Architecture (85)
    • Technical Implementation (23)
  • 2025 (12)
  • 2024 (8)
  • 2023 (18)
  • 2022 (13)
  • 2021 (3)
  • 2020 (8)
  • 2019 (8)
  • 2018 (23)
  • 2017 (17)
  • 2016 (40)
  • 2015 (37)
  • 2014 (25)
  • 2013 (28)
  • 2012 (24)
  • 2011 (30)
  • 2010 (42)
  • 2009 (25)
  • 2008 (13)
  • 2007 (33)
  • 2006 (26)

Ab Testing Adobe Adobe Analytics Adobe Target AEM agile-methodologies Analytics architecture-patterns CDP CMS coding-practices content-marketing Content Supply Chain Conversion Optimization Core Web Vitals customer-education Customer Data Platform Customer Experience Customer Journey DAM Data Layer Data Unification documentation DXP Individualization java Martech metrics mobile-development Mobile First Multichannel Omnichannel Personalization product-strategy project-management Responsive Design Search Engine Optimization Segmentation seo spring Targeting Tracking user-experience User Journey web-development

©2025 CMO & CTO | WordPress Theme by SuperbThemes