Inheriting Lifecycle Data the Smart Way: From Applications to IT Components
Copying lifecycle dates from Applications to IT Components fails the moment relations go m:n. Here's how to merge them into a correct support envelope — and enable it as a switch.
In a healthy LeanIX inventory, lifecycle dates live where people actually maintain them — usually on Applications. Business owners know when an app goes live, when it's being phased out, when it hits end-of-life. So far, so good.
But your IT Components — the databases, middleware, runtimes, and libraries underneath those applications — often have empty lifecycle fields. Nobody maintains them directly, because the truth about "how long do we still need this component?" is really a function of the applications sitting on top of it.
So the obvious idea is: inherit the lifecycle from Application down to IT Component. Copy the dates across the relation and you're done.
Except you're not done. Because reality is m:n.
Why 1:1 copying breaks
One IT Component (say, a PostgreSQL cluster) typically serves many applications. And one application can depend on many components. That's a many-to-many relation.
Now ask the deceptively simple question: what is the lifecycle of that PostgreSQL cluster?
- App A goes end-of-life in 2026.
- App B goes end-of-life in 2029.
- App C is still in "plan" and goes active in 2027.
If you naively copy from the "first" or "latest" application, you get a wrong answer. The component's real support obligation is the union of all the demands placed on it:
- It has to be active as early as the earliest application needs it.
- It can only reach end-of-life once the last application has let go of it.
In other words: you don't pick one source lifecycle. You merge them — and each phase follows its own rule (min vs. max).
The smart merge logic
Here's the core of how our platform computes the resulting lifecycle when many sources feed one target:
def merge_lifecycles(self, lifecycles):
# union of support obligations across all dependents
lifecycle = {
"plan": min(lc["plan"] for lc in lifecycles if "plan" in lc),
"phaseIn": min(lc["phaseIn"] for lc in lifecycles if "phaseIn" in lc),
"active": min(lc["active"] for lc in lifecycles if "active" in lc),
"phaseOut": max(lc.get("phaseOut", "") for lc in lifecycles),
"endOfLife": max(lc.get("endOfLife", "") for lc in lifecycles),
}
return lifecycle
The intuition:
- plan — earliest (min): The component must be planned as soon as any app needs planning.
- phaseIn — earliest (min): It must be rolling in before the first app goes live.
- active — earliest (min): It must be live before the earliest-active app.
- phaseOut — latest (max): It can't start winding down until the last app starts winding down.
- endOfLife — latest (max): It can only die once the last app is gone.
That single asymmetry — min for the early phases, max for the late phases — is the whole trick. It produces the envelope that covers every dependent application.
The subtle bug this avoids
There's one more trap. The five phase dates don't all come from the same source application. active might come from App A, while endOfLife comes from App B. Merge them blindly and you can produce an impossible, non-chronological lifecycle — e.g. a phaseIn date that lands after the merged active date.
So the real implementation sanity-checks the merged result against active and discards any phase date that would violate chronological order, rather than emitting a broken lifecycle:
active = lifecycle["active"]
if active:
if lifecycle["plan"] > active: lifecycle["plan"] = ""
if lifecycle["phaseIn"] > active: lifecycle["phaseIn"] = ""
if lifecycle["phaseOut"] and lifecycle["phaseOut"] < active: lifecycle["phaseOut"] = ""
if lifecycle["endOfLife"] and lifecycle["endOfLife"] < active: lifecycle["endOfLife"] = ""
A merged lifecycle that a human would reject is worse than no lifecycle at all — because it silently poisons every report and roadmap built on top of it.
When you don't want to merge
Sometimes merging is exactly wrong. If a component is shared by ten applications, you might not want its lifecycle driven by all of them — maybe you only trust the inheritance when there's an unambiguous 1:1 mapping. That's a legitimate governance choice.
The engine supports this with a custom filter expression. This one only inherits from source applications that have a single component relation, and otherwise leaves the component untouched:
return [source_fs[src_field]
for source_fs in fs.relation(target_to_source_relation)
if len(source_fs[relation]) <= 1] or None
Returning None tells the engine "skip this one" — so ambiguous, heavily-shared components are left for a human to decide, instead of being guessed.
The good news: it's already a switch
Here's the part that matters if you run the Aronis Automation Platform for SAP LeanIX: you don't have to write any of this. The lifecycle-merge logic is now a standard, built-in capability. You just turn it on with a small configuration:
{
"name": "Set ITComponent Lifecycle based on Application Lifecycle",
"fs_types": ["Application", "ITComponent"],
"relation": "relApplicationToITComponent",
"fields": [
{
"name": "lifecycle",
"filter": [
"return [source_fs[src_field] for source_fs in fs.relation(target_to_source_relation) if len(source_fs[relation]) <= 1] or None"
]
}
]
}
Point it at the relation, name the lifecycle field, optionally add a filter for your governance rules — done. From then on, whenever an application's lifecycle changes, the connected IT Components recompute automatically via webhook. No nightly batch, no manual copy-paste, no stale roadmaps.
Takeaways
- Lifecycle inheritance across
m:nrelations is not a copy — it's a merge. - Use min for early phases (plan/phaseIn/active) and max for late phases (phaseOut/endOfLife) to get the true support envelope.
- Validate the merged result for chronological sanity — a plausible-but-impossible lifecycle is worse than an empty one.
- Decide deliberately when not to inherit (e.g. only trust 1:1 mappings) and let humans handle the ambiguous cases.
- In the Aronis Automation Platform, this is a standard feature you simply enable — no custom code required.
Want to see this running on your own workspace? Get in touch.